多线程的使用及介绍

一、线程与进程的概念

1、进程

  • 当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。
    而一个进程又是由多个线程所组成的。

    进程:可以理解为正在运行中的程序

2、线程

  • 线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的, 即不同的线程可以执行同样的函数。

    小结:进程是正在运行的程序,线程是基于进程的,也就是进程的进一步划分,线程结束了,进程不一定结束,但是如果进程结束了,运行在这个进程中的线程一定结束,也可以这么理解,一个线程必定有一个进程,而一个进程未必只有一个线程

3、两者之间的区别

在这里插入图片描述

二、单线程与多线程的区别

  • 多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,
    也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
  • 多线程的好处:
    可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,
    这样就大大提高了程序的效率。

三、 线程之间执行顺序是无序的

  • 俩个或者多个线程去抢占cpu资源,谁先抢到,谁先执行

四、实现线程的三种方式

1、实现Thread方法

  • 启动线程:如果想正确的启动线程,是不能直接调用run()方法,应该调用从Thread类中继承而得到start()方法,才可以启动线程。

在这里插入图片描述

package all.demo.cn.多线程;

/**
 * 第一中线程的写法
 * 继承 Thread方法
 * */
class MyThread extends Thread{
    public MyThread() {
        /**
         * 构造器里面的调用 相当于是运行了main方法线程
         * 因为一开始主线程是main方法中先调用出来,而MyThread方法还未创建成功
         * */
        Thread thread = Thread.currentThread();
        System.out.print("构造器中线程Id:"+thread.getId()+"\t");
        System.out.print("构造器中线程name:"+thread.getName()+"\n");
    }

    @Override
    public void run() {
        //调用run方法,实际就是在调用start方法中,调用了
        Thread thread = Thread.currentThread();
        System.out.print("第一种线程Id:"+thread.getId()+"\t");
        System.out.print("第一种线程name:"+thread.getName()+"\n");
    }
}

public class MainTest {
    public static void main(String[] args) {
        //第一种写法
        MyThread myThread = new MyThread();
        myThread.start();

        myThread.run();


        /**
         * 第二种写法
         * 实现Runnable
         * */
        Thread thread = new Thread(() -> {
            System.out.print("第二种线程id:"+ Thread.currentThread().getId()+"\t");
            System.out.print("第二种线程name:"+Thread.currentThread().getName()+"\n");
        },"AAAA");
        thread.start();
    }
}

2、start方法与run方法的区别

  • start()方法来启动线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;通过调用Thread类的start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行操作的, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。
  • run()方法当作普通方法的方式调用。程序还是要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码; 程序中只有主线程——这一个线程, 其程序执行路径还是只有一条, 这样就没有达到写线程的目的。
    记住:多线程就是分时利用CPU,宏观上让所有线程一起执行 ,也叫并发

3、实现Runnable方法

在这里插入图片描述

class MyRunnable implements Runnable {
    /**
     * 子线程执行主体
     * */
    @Override
    public void run() {
        System.out.println("Runnable线程名字 = "+Thread.currentThread().getName()
                +"Runnable线程ID = "+Thread.currentThread().getId());
    }
}

public class MainTest {
    public static void main(String[] args) {
        //创建实现类对象
        MyRunnable r=new MyRunnable();
        //创建Thread类对象
        Thread t1=new Thread(r);
        //启动子线程
        t1.start();
        
        
        
        //局部内部类的写法:
		class LocalMyRunnable implements Runnable{
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("子线程名字="+Thread.currentThread().getName()+"  子线程ID="+Thread.currentThread().getId());
			}
		}
		LocalMyRunnable localR=new LocalMyRunnable();
		new Thread(localR).start();
        
        
        //通过匿名内部类,创建子线程
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("子线程名字="+Thread.currentThread().getName()+"  子线程ID="+Thread.currentThread().getId());
			}
		}).start();
        
        
		
		//lambda表达式创建子线程
		new Thread(()->System.out.println("子线程名字="+Thread.currentThread().getName()+"  子线程ID="+Thread.currentThread().getId())).start();
    }
}

4、线程运行原理

  • 以前的main方法中都是执行压栈执行的,现在的线程不一样了,只要执行了start方法都会新开辟一个栈内存空间,所以新的线程与主线程的运行就有了随机性

在这里插入图片描述

5、Thread与Runnable接口两者的区别

在这里插入图片描述

在这里插入图片描述

6、实现Callable方法

  • 使用的体系是

(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。

(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。

(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

public class MyCallable implements Callable {
	@Override
	public Object call() throws Exception {
		int sum = 0;
		for (int i = 1; i < 101; i++) {
			sum += i;
			//获得当前执行线程的名称
			System.out.println(Thread.currentThread().getName()+"---->"+sum);
		}
		return sum;
	}
}
//测试类:
public class Test4 {
	public static void main(String[] args) throws Exception {
		//创建一个线程对象
		MyCallable mc = new MyCallable();
		//创建一个FutureTask对象
		FutureTask<Integer> ft = new FutureTask<Integer>(mc);
		//创建一个FutureTask对象
		FutureTask<Integer> ft2 = new FutureTask<Integer>(mc);
		//得到返回值
		new Thread(ft,"线程A:").start();//启动线程
		new Thread(ft2,"线程B:").start();
		System.out.println(ft.get());
		System.out.println(ft2.get());
	}
}

7、三种线程创建的对比

采用实现Runnable、Callable接口的方式创见多线程时,

优势是:

1.线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。

2.在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

劣势是:

1.编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

使用继承Thread类的方式创建多线程时

优势是:

3.编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

劣势是:

  1. 线程类已经继承了Thread类,所以不能再继承其他父类。

推荐使用:Runnable Callable(有返回值时用他否则都用Runnable),

8、判断线程是否启动

在这里插入图片描述

public class Test7 {
	public static void main(String[] args) {
		RunnableDemo run = new RunnableDemo();

		Thread th = new Thread(run);
		boolean a1 = th.isAlive();//false
		System.out.println(a1);
		th.start();
		boolean a2 = th.isAlive();//true
		System.out.println(a2);

	}

}

9、线程的强制运行

a、使用join()方法

  • 使线程从运行态转换成就绪态
public class Test8 {
	public static void main(String[] args) {
		Mythread3 th = new Mythread3();
		th.start();
			
		for(int i=0;i<100;i++) {
			if(i>10) {
				try {
					th.join();//当主线程循环大于10时,就让th执行完成后再执行
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+"--->"+i);
			
		}
	}
}
class Mythread3 extends Thread{
	@Override
	public void run() {
		for(int i=0;i<100;i++) {
			System.out.println(this.getName()+"--->"+i);
		}
	}
}

10、线程的中断

a、调用interrupt()方法

  • 方法可在需要中断的线程本身中调用,也可在其他线程中调用需要中断的线程对象的该方法。
public class Test11 {
	public static void main(String[] args) {
		Runnable1 run = new Runnable1();
		Thread th = new Thread(run, "线程A");
		th.start();// 启动线程
		
		//让主线程休眠
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		th.interrupt();// 让线程终断

	}
}



11、线程睡眠的方式

a、sleep方法

  • sleep调用时不释放锁
  • sleep 从运行态转换到阻塞态
  • sleep最好不要作用于同步锁机制,因为此sleep在睡眠期间是 不会释放锁 ,导致其他线程也是执行不了
package all.demo.cn.多线程;

class Runnable1 implements Runnable {

	@Override
	public void run() {
		System.out.println("1.进入run方法");
		try {
			Thread.sleep(3000);
			System.out.println("2.已经休眠完成");
		} catch (InterruptedException e) {
			System.out.println("3.休眠被终止了");
			return;
		}

		System.out.println("4.run方法正常结束");

	}

}

class MySleepRunnable implements Runnable {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 10 ; i++) {
                System.out.println(Thread.currentThread().getId()+"+++++++>"+i);
                if (i>5){
                    System.out.println(Thread.currentThread().getId()+"----->"+i);
                    Thread.sleep(5000);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class SleepTest {
    public static void main(String[] args) {
        MySleepRunnable mySleepRunnable = new MySleepRunnable();
        new Thread(mySleepRunnable).start();
    }
}

b、wait方法

package all.demo.cn.多线程;

public class WaitTest {

    public static void main(String[] args) {
        Object o  = new Object();
        new Thread(() -> {
            for (int i = 0; i < 5 ; i++) {
                System.out.println(Thread.currentThread().getName()+"i的值"+i);
                synchronized (o){
                    try {
                        System.out.println("使用了wait方法");
                        o.wait();
                        
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        },"AA").start();
    }
}

12、yield方法

  • yield方法使到当前线程暂停,让出cpu资源给其他的线程。但是和sleep()方法不同的是,它不会进入到阻塞状态,而是进入到就绪状态,让系统的线程调度器重新调度器重新调度一次
    在这里插入图片描述
package yield方法;

//创建一个线程类
class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("子线程
                    ID = "+Thread.currentThread().getId()+"i = "+i);
            if (i == 30) {
                System.out.println("子线程调用yield方法");
                Thread.yield();
            }
        }
    }
}

public class MainTest {

    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();
        for (int j = 0; j < 100; j++) {
            if (j == 60) {
//当前线程暂停(从运行态---->就绪态)
                System.out.println("主线程调用yield方法");
                Thread.yield();
            }
            System.out.println("主线程
                    ID = "+Thread.currentThread().getId()+"j = "+j);
        }
    }
}

12、如何结束线程

在这里插入图片描述

在这里插入图片描述

/**
 * 用标志位结束线程
 */
public class Test6 {
	public static void main(String[] args) {
		MyRunnable6 mr = new MyRunnable6();
		new Thread(mr, "线程1").start();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		// 结束线程
		mr.stop();
	}
}

class MyRunnable6 implements Runnable {
	private boolean flag = true;// 定义一个标志位
	@Override
	public void run() {
		int i = 0;
		while (flag) {
			System.out.println(Thread.currentThread().getName() + "--->" + i++);
		}
	}

	// 编写一个方法,可以用来结束线程
	public void stop() {
		this.flag = false;
	}
}

package day806.cn.com;

class MyThread7 extends Thread {
    //添加 一个终止线程的标记变量
    public volatile boolean isFlag = false;
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("i=" + i);
            //睡眠1秒,执行一次
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (!isFlag) {
                break;
            }
        }
    }
}

public class MainTest6 {
    public static void main(String[] args) {
        //创建线程类的对象
        MyThread7 t = new MyThread7();
        t.start();
        try {
            //主线程睡眠5秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        t.isFlag = true;
        //不要使用stop方法,终止线程
//        t.stop();

    }

}


五、线程的生命周期

在这里插入图片描述

在这里插入图片描述

  • 线程的状态及说明:
    • 新建状态(New):新创建了一个线程对象
    • 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
    • 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
    • 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
      • 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
      • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
      • 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
    • 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

六、线程的优先级

1、简介

  • 每个线程执行时都具有一定的优先级,优先级高的线程获得较多的执行机会,而优先级低的线程则获得较少的机会。
  • 每个线程默认的优先级都与创建它的父线程的优先级相同,在默认情况下,main线程具有普通优先级,有main线程创建的子线程野具有普通优先级。
  • Thread类提供了setPriority(int newPriority)、getPriority()方法来设置和返回指定线程的优先级,其setPriority()方法的参数是一个整数,范围在1~10之间
package all.demo.cn.多线程;
class MyRunnabless implements Runnable{
    private String name;

    public MyRunnabless(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 0; i <= 15; i++) {
            int sum = i*10;
            System.out.println(name+"跑了"+sum+"米");
            if (sum==100){
                break;
            }
        }
    }
}

public class PriorityTest {
    public static void main(String[] args) {
        MyRunnabless rabbit = new MyRunnabless("兔子");
        Thread thread = new Thread(rabbit);
        thread.setPriority(Thread.MIN_PRIORITY);
        thread.start();

        MyRunnabless tortoise = new  MyRunnabless("乌龟");
        Thread thread1 = new Thread(tortoise);
        thread1.setPriority(Thread.MAX_PRIORITY);
        thread1.start();
    }
}


七、后台线程

  • 简介
    • 有一种线程,它在后台运行,它的任务是为其他的线程提供服务,这
      种线程被称为“后台线程”(Daemon Thread)。
  • 后台线程有个特征
    • 后台线程有个特征,如果所有的前台线程都死亡,后台线程会自动
      死亡。
  • 设置后台线程方法
    • setDaemon(true)
package all.demo.cn.多线程;

class MyThreaddd extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            //睡眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {

                e.printStackTrace();

            }
            System.out.println("thread id=" + Thread.currentThread().getId() + " i=" + i);
        }
    }
}

public class DaemonTest {
    public static void main(String[] args) {
        MyThreaddd myThreaddd = new MyThreaddd();
        myThreaddd.setDaemon(true);
        myThreaddd.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main thread end ");

    }
}

八、线程安全问题

  • 模拟一个银行业务,出现的线程不安全
package all.demo.cn.多线程;

class MyAccount{
    private int balance;
    private String account; //用户名

    public MyAccount(int balance, String account) {
        this.balance = balance;
        this.account = account;
    }

    public int getBalance() {
        return balance;
    }

    public void setBalance(int balance) {
        this.balance = balance;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }
}


class MyThreadNoSyn extends Thread{
    private MyAccount myAccount;
    private int mun; //取出的金额

    public MyThreadNoSyn(MyAccount myAccount, int mun) {
        this.myAccount = myAccount;
        this.mun = mun;
    }

    @Override
    public void run() {
        if (myAccount.getBalance()>=mun){
            int s = myAccount.getBalance()-mun;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("用户总额:"+myAccount.getBalance()+"取款金额:"+mun+"余额:"+s);
            myAccount.setBalance(s);

        }else {
            System.out.println("账号名 ="+myAccount.getBalance()+" 余额不足,无法正常取款");
        }
    }
}

public class NoSynchTest {
    public static void main(String[] args) {
        MyAccount myAccount = new MyAccount(1000,"AA");
        MyThreadNoSyn myThreadNoSyn = new MyThreadNoSyn(myAccount,200);
        MyThreadNoSyn myThreadNoSyn2 = new MyThreadNoSyn(myAccount,800);
        myThreadNoSyn.start();
        myThreadNoSyn2.start();
    }
}

在这里插入图片描述

1、如何解决线程安全

a、使用同步代码块

  • 为了防止多个线程同时访问和修改同一个对象,Java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块。同步代码块的语法格式如下:
package all.demo.cn.多线程;

class MyAccount{
    private int balance;
    private String account; //用户名

    public MyAccount(int balance, String account) {
        this.balance = balance;
        this.account = account;
    }

    public int getBalance() {
        return balance;
    }

    public void setBalance(int balance) {
        this.balance = balance;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }
}


class MyThreadNoSyn extends Thread{
    private MyAccount myAccount;
    private int mun; //取出的金额

    public MyThreadNoSyn(MyAccount myAccount, int mun) {
        this.myAccount = myAccount;
        this.mun = mun;
    }

    @Override
    public void run() {
        //添加同步代码块
        //mutex:同步监听器
           /**
           * 同步监听器设置的条件
           * 1、必须是对象
           * 2、共享资源
           * */
        synchronized (myAccount){
            if (myAccount.getBalance()>=mun){
                int s = myAccount.getBalance()-mun;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("用户总额:"+myAccount.getBalance()+"取款金额:"+mun+"余额:"+s);
                myAccount.setBalance(s);

            }else {
                System.out.println("账号名 ="+myAccount.getBalance()+" 余额不足,无法正常取款");
            }
        }

    }
}

public class NoSynchTest {
    public static void main(String[] args) {
        MyAccount myAccount = new MyAccount(1000,"AA");
        MyThreadNoSyn myThreadNoSyn = new MyThreadNoSyn(myAccount,200);
        MyThreadNoSyn myThreadNoSyn2 = new MyThreadNoSyn(myAccount,800);
        myThreadNoSyn.start();
        myThreadNoSyn2.start();
    }
}

b、使用同步方法

  • Java的多线程安全支持还提供了同步方法,同步方法就是使用synchronized关键字来修饰某个方法,则该方法称为同步方法。对于同步方法而言,无需显式指定同步监视器,同步方法的同步监视器是 this ,也就是改对象本身
package all.demo.cn.多线程;

class MyAccount {
    private int balance;
    private String account; //用户名

    public MyAccount(int balance, String account) {
        this.balance = balance;
        this.account = account;
    }

    public int getBalance() {
        return balance;
    }

    public void setBalance(int balance) {
        this.balance = balance;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    /**
     * 定义一个同步方法
     * */
    public synchronized void drawMoney(int money) {
        if(this.getBalance()>=money)
        {
           //局部变量(每个线程分配一份m的变量存储)
            int m=this.getBalance()-money;
          //睡眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {

                e.printStackTrace();
            }
            //account的balance属性才是共享变量值
            this.setBalance(m);
            System.out.println("账号名 ="+this.getAccount()+" 取款金额="+money+" 当前余额 ="+this.getBalance()+"线程id="+Thread.currentThread().getId()+" m="+m);
        }else
        {
            System.out.println("账号名 ="+this.getAccount()+" 余额不足,无法正常取款");
        }
    }
}


class MyThreadNoSyn extends Thread {
    private MyAccount myAccount;
    private int mun; //取出的金额

    public MyThreadNoSyn(MyAccount myAccount, int mun) {
        this.myAccount = myAccount;
        this.mun = mun;
    }

    @Override
    public void run() {
        /**
         * 同步方法的线程安全
         * */
        myAccount.drawMoney(mun);
    }
}

public class NoSynchTest {
    public static void main(String[] args) {
        MyAccount myAccount = new MyAccount(1000, "AA");
        MyThreadNoSyn myThreadNoSyn = new MyThreadNoSyn(myAccount, 200);
        MyThreadNoSyn myThreadNoSyn2 = new MyThreadNoSyn(myAccount, 800);
        myThreadNoSyn.start();
        myThreadNoSyn2.start();
    }
}

c、使用显示锁

package all.demo.cn.多线程;

import java.util.concurrent.locks.ReentrantLock;

class MyAccount {
    private int balance;
    private String account; //用户名

    public MyAccount(int balance, String account) {
        this.balance = balance;
        this.account = account;
    }

    public int getBalance() {
        return balance;
    }

    public void setBalance(int balance) {
        this.balance = balance;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    /**
     * 定义一个可重现锁
     */
    private ReentrantLock lock = new ReentrantLock();

    /**
     * 定义一个普通的方法逻辑
     */

    public void drawMoney(int money) {
        lock.lock(); //加锁
        if (this.getBalance() >= money) {
            //局部变量(每个线程分配一份m的变量存储)
            int m = this.getBalance() - money;
            //睡眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {

                e.printStackTrace();
            }
            //account的balance属性才是共享变量值
            this.setBalance(m);
            System.out.println("账号名 =" + this.getAccount() + " 取款金额=" + money + " 当前余额 =" + this.getBalance() + "线程id=" + Thread.currentThread().getId() + " m=" + m);
        } else {
            System.out.println("账号名 =" + this.getAccount() + " 余额不足,无法正常取款");
        }
        lock.unlock();//解锁
    }
}


class MyThreadNoSyn extends Thread {
    private MyAccount myAccount;
    private int mun; //取出的金额

    public MyThreadNoSyn(MyAccount myAccount, int mun) {
        this.myAccount = myAccount;
        this.mun = mun;
    }

    @Override
    public void run() {
        /**
         * 同步方法的线程安全
         * */
        myAccount.drawMoney(mun);
    }
}

public class NoSynchTest {
    public static void main(String[] args) {
        MyAccount myAccount = new MyAccount(1000, "AA");
        MyThreadNoSyn myThreadNoSyn = new MyThreadNoSyn(myAccount, 200);
        MyThreadNoSyn myThreadNoSyn2 = new MyThreadNoSyn(myAccount, 800);
        myThreadNoSyn.start();
        myThreadNoSyn2.start();
    }
}

九、死锁问题

  • 所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进
package all.demo.cn.多线程;
public class DieLock {
    public static void main(String[] args) {
        Object a = new Object();
        Object b = new Object();

        Thread aa = new Thread(() -> {
            synchronized (a){
                System.out.println("A线程运行中");
                synchronized (b){
                    System.out.println("A线程结束");
                }
            }
        });



        Thread bb = new Thread(() -> {
            synchronized (b){
                System.out.println("B线程运行中");
                synchronized (a){
                    System.out.println("B线程结束");
                }
            }
        });

        aa.start();
        bb.start();
    }
}

十、线程通信

  • 当线程在系统内运行时,线程的调度具有一定的透明性,程序通常无法准确控制线程的轮换执行,但Java也提供了一些机制来保证线程协调运行

    线程通信通常用于生产者/消费者模式

a、传统的线程通信

假设现在系统中有两个线程,这两个线程分别代表存款者和取钱者----现在有一种特殊的要求:存款者和取钱者不断地重复存款、取款动作,而且要求每当存款者将钱存入指定账户后,取钱者就立即取出该笔钱。不允许连续存款和取款。

为了实现这种功能,可以借助于Object类提供的wati()、notify()、notifyAll()三个方法。下面是关于这三个方法的解释:

  • wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()或notifyAll()来唤醒该线程(带参数则等待指定时间后自动苏醒)
  • notify():唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则随机选择一个线程唤醒
  • notifyAll():唤醒在此同步监视器上等待的所有线程
package all.demo.cn.多线程;

/**
 *账户类
 */
class Account {
    private double balance;//存款
    private int id;//编号
    private boolean flag=true;//判断账户是否有存款

    public Account() {
    }

    public Account(double balance, int id) {
        this.balance = balance;
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public double getBalance() {
        return balance;
    }

    /**
     * 取钱
     */
    public synchronized void draw(int money){
        if (flag){//账户有存款
            System.out.println(DrawThread.currentThread().getName()+"取钱"+money);
            //取钱
            balance-=money;
            System.out.println("账户余额为"+balance);
            //账户无存款
            flag=false;
            //唤醒存钱线程
            notifyAll();
        }else{//账户没存款
            //线程等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 存钱
     */
    public synchronized void deposit(int money){
        if (!flag){//账户没存款
            System.out.println(DrawThread.currentThread().getName()+"存钱"+money);
            //存钱
            balance+=money;
            System.out.println("账户余额为"+balance);
            //账户有存款
            flag=true;
            //唤醒取款
            notifyAll();
        } else{//账户有存款
            //线程等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
/**
 * 存钱线程
 */
class DepositThread extends Thread{
    private Account account;//账户
    private String name;//线程名
    private int money;//存款金额

    public DepositThread(String name, Account account, int money) {
        super(name);
        this.account = account;
        this.money = money;
    }

    @Override
    public void run() {
        //存钱十次
        for (int i = 0; i < 10; i++) {
            account.deposit(money);
        }
    }
}
/**
 * 取钱线程
 */
class DrawThread extends Thread{
    private Account account;//账户
    private String name;//线程名
    private int money;//取款金额

    public DrawThread(String name, Account account, int money) {
        super(name);
        this.account = account;
        this.money = money;
    }

    @Override
    public void run() {
        //取款十次
        for (int i = 0; i < 10; i++) {
            account.draw(money);
        }
    }
}
/**
 *测试类
 */
public class CommunTest {

    public static void main(String[] args) {
        //账户
        Account account=new Account(1000,1);
        //创建线程
        DepositThread depositThread=new DepositThread("lsy",account,1000);
        DrawThread drawThread=new DrawThread("yl",account,1000);
        //启动线程
        depositThread.start();
        drawThread.start();
    }

}

b、使用Condition控制线程通信

  • Condition实例被绑定在一个Lock对象上。要获得特定Lock实例的Condition实例,调用Lock对象的newCondition()即可。Condition提供了如下三个方法:
    • await():类似wait(),导致当前线程等待,直到其他线程调用该Condition的signal()或signalAll()来唤醒该线程
    • signal():唤醒在此Lock对象上等待的单个线程。如果所有线程都在此Lock对象上等待,则随机选择一个线程唤醒
    • signalAll():唤醒在此Lock对象上等待的所有线程
package Condition用法;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/*
 * 定义一个账号类
 * */
class Account {
    private String name;
    private double amount;
    private boolean flag = true;
    //定义一个可重现锁对象
    ReentrantLock lock;
    //定义一个Condition对象
    Condition condition;

    public Account() {
        lock = new ReentrantLock();
        condition = lock.newCondition();
    }

    public Account(String name, double amount) {
        this.name = name;
        this.amount = amount;
        lock = new ReentrantLock();
        condition = lock.newCondition();
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return the amount
     */
    public double getAmount() {
        return amount;
    }

    /**
     * @param amount the amount to set
     */
    public void setAmount(double amount) {
        this.amount = amount;
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "Account [name=" + name + ", amount=" + amount
                + "]";
    }

    /*
     * 定义取款的方法
     * */
    public void draw(double money) {
//显式加锁
        lock.lock();
        if (flag) {
            try {
                condition.await();
            } catch (InterruptedException e) {
// TODO Auto-generated catch block
                e.printStackTrace();
            }
        } else {
            setAmount(getAmount() - money);
            System.out.println("取款成功 当前余额=" +
                    getAmount() + " 取款=" + money);
            flag = true;
//唤醒存款线程,你该存款
            condition.signal();
        }
//释放锁
        lock.unlock();
    }

    /*
     * 定义存款的方法
     * */
    public void doposit(double money) {
//显式加锁
        lock.lock();
//flag默认等于true
        if (!flag) {
//存款处于等待,直到被取款线程唤醒
            try {
                condition.await();
            } catch (InterruptedException e) {
// TODO Auto-generated catch block
                e.printStackTrace();
            }
        } else {
            setAmount(getAmount() + money);
            System.out.println("存款成功 当前余额=" +
                    getAmount() + " 存款=" + money);
            flag = false;
//唤醒取款的线程,你该取款
            condition.signal();
        }
//释放锁对象
        lock.unlock();
    }
}

/*
 * 取款线程
 */
class DrawThread extends Thread {
    private Account account;
    private double money;

    public DrawThread(Account account, double money) {
        this.account = account;
        this.money = money;
    }

    @Override
    public void run() {
        for (int i = 0; i < 11; i++) {
            this.account.draw(money);
        }
    }
}

/*
 * 存款线程
 */
class Dosipoit extends Thread {
    private Account account;
    private double money;

    public Dosipoit(Account account, double money) {
        this.account = account;
        this.money = money;
    }

    @Override
    public void run() {
        for (int i = 0; i < 11; i++) {
            this.account.doposit(money);
        }
    }
}

public class MainTest {
    public static void main(String[] args) {
        Account account = new Account("张三", 0);

        Dosipoit d = new Dosipoit(account, 100);
        DrawThread f = new DrawThread(account, 100);
        d.start();
        f.start();
    }
}

十一、线程池的使用

  • 线程池有四种创建方式

a、newFixedThreadPool

  • 固定大小的线程池,可以指定线程池的大小,该线程池corePoolSize和maximumPoolSize相等,阻塞队列使用的是LinkedBlockingQueue,大小为整数最大值。该线程池中的线程数量始终不变,当有新任务提交时,线程池中有空闲线程则会立即执行,如果没有,则会暂存到阻塞队列。对于固定大小的线程池,不存在线程数量的变化。同时使用无界的LinkedBlockingQueue来存放执行的任务。当任务提交十分频繁的时候,LinkedBlockingQueue迅速增大,存在着耗尽系统资源的问题。而且在线程池空闲时,即线程池中没有可运行任务时,它也不会释放工作线程,还会占用一定的系统资源,需要shutdown。
public class FixPoolDemo {
    private static Runnable getThread(final int i) {
        return new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(i);
            }
        };
    }
    public static void main(String args[]) {
        ExecutorService fixPool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            fixPool.execute(getThread(i));
        }
        fixPool.shutdown();
    }
}

b、newSingleThreadExecutor

  • 单个线程线程池,只有一个线程的线程池,阻塞队列使用的是LinkedBlockingQueue,若有多余的任务提交到线程池中,则会被暂存到阻塞队列,待空闲时再去执行。按照先入先出的顺序执行任务。
public class SingPoolDemo {
    private static Runnable getThread(final int i){
        return new Runnable() {
            @Override
            public void run() {
                try {

                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(i);
            }
        };
    }

    public static void main(String args[]) throws InterruptedException {
        ExecutorService singPool = Executors.newSingleThreadExecutor();
        for (int i=0;i<10;i++){
            singPool.execute(getThread(i));
        }
        singPool.shutdown();
    }

c、newCachedThreadPool

  • 缓存线程池,缓存的线程默认存活60秒。线程的核心池corePoolSize大小为0,核心池最大为Integer.MAX_VALUE,阻塞队列使用的是SynchronousQueue。是一个直接提交的阻塞队列, 他总会迫使线程池增加新的线程去执行新的任务。在没有任务执行时,当线程的空闲时间超过keepAliveTime(60秒),则工作线程将会终止被回收,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销。如果同时又大量任务被提交,而且任务执行的时间不是特别快,那么线程池便会新增出等量的线程池处理任务,这很可能会很快耗尽系统的资源。
public class CachePool {
    private static Runnable getThread(final int i){
        return new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                }catch (Exception e){

                }
                System.out.println(i);
            }
        };
    }

    public static  void main(String args[]){
        ExecutorService cachePool = Executors.newCachedThreadPool();
        for (int i=1;i<=10;i++){
            cachePool.execute(getThread(i));
        }
    }
}

这里没用调用shutDown方法,这里可以发现过60秒之后,会自动释放资源

d、newScheduledThreadPool

  • 定时线程池,该线程池可用于周期性地去执行任务,通常用于周期性的同步数据。

    scheduleAtFixedRate:是以固定的频率去执行任务,周期是指每次执行任务成功执行之间的间隔。

    schedultWithFixedDelay:是以固定的延时去执行任务,延时是指上一次执行成功之后和下一次开始执行的之前的时间。

public class ScheduledExecutorServiceDemo {
    public static void main(String args[]) {

        ScheduledExecutorService ses = Executors.newScheduledThreadPool(10);
        ses.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(4000);
                    System.out.println(Thread.currentThread().getId() + "执行了");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, 0, 2, TimeUnit.SECONDS);
    }
}

十二、ThreadLocal类应用

  • ThreadLocal为解决多线程程序的并发问题提供了一种新的思路,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
class MyRunnable implements Runnable {
    private int i = 0; //ThreadLocal默认初始化数据值0 
    ThreadLocal<Integer> threadId = new ThreadLocal<Integer> () {       
    @Override
    protected Integer initialValue() {
    return 0;
    } 
};
@Override public void run() { 
    for (int j = 0; j < 10; j++) { 
        //每个线程都有一份threadId变量的副本
        //针对副本+1操作
        threadId.set(threadId.get()+1);
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //针对副本-1操作
        threadId.set(threadId.get()-1);
        System.out.println("thread id=" +Thread.currentThread().getId() + " i=" + threadId.get()); 
        } 
    } 
}
public class MainTest { 
    public static void main(String[] args) {
        MyRunnable r = new MyRunnable(); 
        // 创建线程一
        new Thread(r).start(); 
        // 创建线程二 
        new Thread(r).start(); 
    } 
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值