黑马程序员_多线程及单例模式
------- android培训、java培训、期待与您交流! ----------
进程与线程:
进程,正在运行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径
,或者叫一个控制单元。
线程,就是进程中的一个独立的控制单元。线程在控制着进程的执行。一个进程中至少有一个线程。
其实更细节说明jvm启动不止一个线程,还有负责垃圾回收机制的线程。
线程都有自己的默认名称,也可以通过setName()方法,或者通过构造方法来给线程设置自定义的名称。
启动线程的两种方式:
第一种方式:
继承Thread类:
1、定义类继承Thread。
2、复写Thread类中的run方法。目的:将自定义代码存储在run方法。让线程运行。
3,调用线程的start方法,该方法两个作用:启动线程,调用run方法。
Thread类用于描述线程。
该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法。
也就是说Thread类中的run方法,用于存储线程要运行的代码。
class Demo extends Thread {
@Override
public void run() { //重写Thread的run()方法
System.out.println("demo run");
}
}
public static void main(String[] args) throws IOException {
Demo demo = new Demo();
//开启线程的同时并执行该线程下的run方法,run方法并不能启动一个新的线程。
demo.start();
}
第二种方式:
实现Runnable接口:
1、定义类实现Runnable接口
2、覆盖Runnable接口中的run方法。将线程要运行的代码存放在该run方法中。
3、通过Thread类建立线程对象。
4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
为什么要将Runnable接口的子类对象传递给Thread的构造函数。
因为,自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去执行对象的run方
法。就必须明确该run方法所属对象。
5、调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
public class Demo {
public static void main(String[] args) throws IOException {
Demo d = new Demo();
Thread t1 = new Thread(d);//创建线程,并将Runnable接口的子类对象传递给Thread
t1.start();//启动线程
}
}
class Demo implements Runnable {
@Override
public void run() {//实现接口的run方法
System.out.println("demo run");
}
}
两种方式区别:
1、实现方式的好处:避免了单继承的局限性;在定义线程时,建议使用实现方式。
2、继承方式的线程代码存放在Thread子类的run方法中。
3、实现方式的线程代码存放在接口的子类的run方法中。
线程状态分析图:
多线程的安全问题:
导致安全问题的出现的原因:
1、多个线程访问出现延迟。
2、线程随机性。
同步(synchronized):
1、同步代码块:
synchronized(对象)
{
需要同步的代码;
}
2、同步函数
1、同步函数的锁是this。
2、如果同步函数被静态修饰,则锁为Class。
同步的前提:
1、同步需要两个或者两个以上的线程。
2、多个线程使用的是同一个锁。
未满足这两个条件,不能称其为同步。
同步的弊端:
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的
运行效率。
class Ticket implements Runnable
{
private int tick = 1000;
Object obj = new Object();//锁对象,锁可以是任何对象,任何对象又都继承于Object
public void run()
{
while(true)
{
synchronized(obj)//同步代码块
{
if(tick>0)
{
System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
}
}
}
}
}
同步中出现的死锁现象:
两把锁嵌套使用则有可能出现死锁。
class Test implements Runnable
{
private boolean flag;//标志位
Test(boolean flag)//构造方法中传入一个标志初始化值
{
this.flag = flag;
}
public void run()
{
if(flag)//如果flag为true,则进入同步代码块,拿到锁locka,若此时另一个线程已经开始运行,并flag的值为false,则拿到了锁lockb
{ // 当拿有locka锁的线程要进入b锁的代码块时,lockb却被另一个线程持有,而此时持有lockb锁的线程也在等待locka锁,这样发生嵌套的情况,
//即会发生死锁情况。
while(true)
{
synchronized(MyLock.locka)
{
System.out.println(Thread.currentThread().getName()+"...if locka ");
synchronized(MyLock.lockb)
{
System.out.println(Thread.currentThread().getName()+"..if lockb");
}
}
}
}
else
{
while(true)
{
synchronized(MyLock.lockb)
{
System.out.println(Thread.currentThread().getName()+"..else lockb");
synchronized(MyLock.locka)
{
System.out.println(Thread.currentThread().getName()+".....else locka");
}
}
}
}
}
}
class MyLock
{
static Object locka = new Object();//定义locka锁对象
static Object lockb = new Object();//定义lockb锁对象
}
class DeadLockTest
{
public static void main(String[] args)
{
//创建并启动两个线程
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}
线程间通信:
就是多个线程在操作同一个资源,但是操作的动作不同。以下用生产者消费者的示例演示线程间通信方法,生产一个消费一个。
class IOtest1 {
public static void main(String[] args) throws InterruptedException {
/**
* 创建4个线程同时执行,其中两个生产者和两个消费者
* 但商品依然是生产一个消费一个的方式进行
*/
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(pro);
Thread t4 = new Thread(con);
//启动这4个线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Resource {
private String name;
private int count = 1;
private boolean flag = false;
/**
* 资源生产方法
* @param name
*/
public synchronized void set(String name) {
while(flag){ //if和while区别:当等待中的线程再次被唤醒时,if不需要再次判断标志位,直接向下执行,而while需要重新判断标志位
try {
this.wait();//线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + "..." + count++;
System.out.println(Thread.currentThread().getName()+"...生产者"+this.name);//打印生产
flag = true;
this.notifyAll(); //notify和notifyAll方法:前者只能唤醒先进入线程池中的,并且持有该锁的线程,而后者可以唤醒线程池中所有持有该锁的线程
}
/**
* 资源消费方法
*/
public synchronized void out() {
while(!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"...消费者"+this.name);//打印消费
flag = false;
this.notifyAll();
}
}
/**
* 生产者类
* @author Administrator
*
*/
class Producer implements Runnable {
private Resource res;
Producer(Resource res) {
this.res = res;
}
@Override
public void run() {
while(true) {
res.set("-商品-");//调用生产方法
}
}
}
/**
* 消费者类
* @author Administrator
*
*/
class Consumer implements Runnable {
private Resource res;
Consumer(Resource res) {
this.res = res;
}
@Override
public void run() {
while(true) {
res.out();//调用消费方法
}
}
}
wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中:
1,这些方法存在与同步中。
2,使用这些方法时必须要标识所属的同步的锁。
3,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
wait(),sleep()有什么区别:
wait():释放cpu执行权,释放锁。
sleep():释放cpu执行权,不释放锁。
停止线程:
只有run方法结束后,线程便会停止。
开启多线程运行,运行代码通常是循环结构,只要控制好循环,就可以让线程停止。
当没有指定的方式让冻结的线程恢复到运行状态是,这时需要对冻结进行清除。
强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。
Thread类提供该方法 interrupt();
守护线程:
1.5版本JDK中的线程锁:
private Lock lock = new ReentrantLock();
Condition con = lock.newCondition();
一个Lock中可以有多个Condition。
单例设计模式:
单例设计模式:解决一个类在内存只存在一个对象。
保证对象唯一:
1、为了避免其他程序过多建立该类对象。先禁止其他程序建立该类对象
2、还为了让其他程序可以访问到该类对象,只好在本类中,自定义一个对象。
3、为了方便其他程序对自定义对象的访问,可以对外提供一些访问方式。
代码体现过程:
1、将构造函数私有化。
2、在类中创建一个本类对象。
3、提供一个方法可以获取到该对象。
饿汉式:
private static Single s = new Single();
private Single() {}
public static Single getInstance() {
return s;
}
懒汉式(解决懒汉式线程安全问题):
class Single {
private static Single s = null;//延迟加载
private Single() {}
public static Single getInstance() {
if(s == null) {//为了提高运行效率,在判断锁之前再判断一次对象是否存在
synchronized(Single.class) {
if(s == null) {
s = new Single();
}
}
}
return s;
}
}