概念
程序 进程 线程
程序:为完成特定任务,用某种语言写的一组指令(代码)的集合,静态代码。如在手机电脑上下载app
进程:程序的一次执行过程,动态过程,有自身的产生,存在,消亡,即生命周期。如点击app,获取cpu资源,运行
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。如安装好的IDEA目录,IDEA文件夹下的静态代码,就是程序,通过点击可执行文件,IDEA就运行起来了,在任务管理器里可以看到IDEA这个进程,内存占用
线程:进程进一步细化为线程,是一个程序内部的一条执行路径,资源调度的最小单位
360安全卫士打开,可以同时进行电脑检测,木马查杀,电脑清理等操作,相当于启动多个线程,内存占用空间就增多了
程序是静态的,进程是动态的
一个进程同时执行多个线程,就是支持多线程的
内存结构:方法区,虚拟机栈,堆,程序计数器
每个线程拥有自己的虚拟机栈和程序计数器,方法区和堆不是一个线程一份,而是一个进程一份,意味着多个线程共享一个进程的方法区和堆
一个进程中的多个线程是共享其中的方法区和堆空间的,可以从同一堆中分配对象,访问相同的变量和对象。实现多个线程之间的通信是很方便的。但多个线程之间操作共享的数据可能会带来安全隐患
要想实现多线程,必须在主线程中创建新的线程对象
单核CPU 多核CPU 并行 并发
多核相较单核,核多同时运算的程序也就多了。单核CPU意味着只有一个核可以用来数据的处理,实际上是假的多线程。在多核CPU出现之前,也可以发现Windows任务管理器里也可以看到许多进程同时都在执行,也是支持多进程的。一个CPU同时执行许多任务,多个进程,同时也是多个线程。最起码一个进程一个线程。本来单个CPU同一时间只能做一件事,但看到现象有同时做好多事。单个CPU多个线程过来让其执行,CPU分别让每个线程先执行一段,执行一遍后,再回头依次执行。因为CPU主频太高,虽然同个时间只能做一件事,但速度很高,像厨师做菜一般,给客人假象是好多厨师在工作。
单核同一个时间只能做一件事,多核同一时间可以做多个事,更好的发挥多线程的效率。现在的服务器都是多核的
对于一个java应用程序而言,运行过程至少启动三个线程,main()主线程,gc()垃圾回收线程,异常处理线程。如果发生异常影响主线程执行,没处理的话,主线程也就停止了。
main方法里调用其他方法如A,在A里有定义变量,出了方法变量也就失效了。main方法要继续执行,发现A方法中一些变量不能用了,垃圾回收器要回收了,这时候就要垃圾回收线程去做,把无用数据清空。
并行:多个CPU同时执行多个任务。如多个人同时做不同的事(不同人做不同事),还如马路上每条车道车辆自己开自己的
并发:一个CPU采用时间片,快速切换不同线程,“同时”执行不同任务。如秒杀,多个人做同件事
对单核CPU而言,只用单个线程先后完成多个任务,肯定比多个线程来完成用的时间更短。比如,从C盘向D盘复制1G数据,E盘向F盘复制1G数据。两种方法,C->D,数据复制完成,再接着E->F;或者在C->D复制的同时,E->F也同时复制着。前者用的时间较短,因为后者相当于多线程,CPU还要快速的去切换花费时间
多线程的优点:
- 提高应用程序的响应,对图形化界面而言,更有意义,更增强了用户体验
- 提高CPU的利用率;多线程,CPU之间快速切换,同时做好多事,相当于不停执行;单线程,一个任务完成,另一个任务紧跟着,要不然之间有间隔
- 代码结构更加清晰,复杂的进程分为多个线程,独立运行,便于理解
多线程使用场景?
程序需要同时执行两个或多个任务;
如main方法内代码执行时,main进程在执行,而垃圾回收进程也会出现,不能等main方法执行完成再进行垃圾回收,防止垃圾过多,内存溢出,就希望main方法对应的主线程执行的同时后台再跟进垃圾回收线程
程序需要实现一些需要等待的任务操作
如美团外卖,一项项菜单列表,图片和文本框显示,可能因网络问题,向下滑动查看时,出现图片加载不出来的问题,实际上图片比文本大很多,一项项数据显示都要一个线程去加载,如果是一个线程,加载图片的事也要在这个线程里做,图片没下载完成就显示不了,而下载图片用专门线程去做,文本内容可能提前显示出来了
需要后台运行的程序
如垃圾回收线程
创建线程方式
1. 创建一个类继承Thread,重写Thread类的run方法(run方法中写线程执行的操作),创建Thread类的子类的对象,通过对象调用start()方法
public class MyThread extends Thread{
@Override
public void run() {
for(int i = 0 ; i < 500; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": "+ i);
}
}
}
}
class Test{
public static void main(String[] args) {
MyThread m1 = new MyThread();
//更改线程名
//m1.setName("线程A");
m1.start();
for(int i = 0 ; i < 500; i++){
if(i % 2 == 0){
System.out.println("main:" + i);
}
}
}
}
先是main方法,主线程。创建m1对象以及start方法的调用,都是主线程完成的。调用完start方法之后,才相当于m1线程启动运行。之后的循环和m1线程属于俩线程里同时运行的。运行结果与电脑CPU切换速度有关,每个人的结果可能不同。
start方法的作用:开启当前线程;调用当前线程的run方法。若对象只调用run方法当作另一线程使用,这是错误的,只是体现方法调用,并没有开启新的线程。
另外,start方法对于一个线程来说只能调用一次,不可以让已经start方法的线程再去执行,如再输入m1.start()。
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
Thread类中源码start方法,首次调用start方法,线程状态为0,执行下面的代码,而代码启动之后,状态不为0,就抛出异常
解决这个问题,再创建这个线程的对象,重新启动
MyThread m2 = new MyThread();
m2.start();
Thread-0: 494
Thread-0: 496
Thread-0: 498
Thread-1: 0
Thread-1: 2
还有如果要创建的多个线程里要做的事不同,创建多个Thread类的子类(各自创建),依照上面的步骤编写代码,分别创建对应的对象,调用start方法。分别在run方法中写要执行的操作
创建Thread类的匿名子类的方式
new Thread(){
@Override
public void run() {
for(int i = 0 ; i < 500; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": "+ i);
}
}
}
}.start();
2. 类实现Runnable接口,重写run方法,创建实现类对象,将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象,Thread类对象调用start方法
public class MyThread3 implements Runnable {
@Override
public void run() {
for(int i = 0; i < 500; i++){
System.out.println("线程A:" + i);
}
}
}
class MyThread4 implements Runnable{
@Override
public void run() {
for(int i = 0; i < 500; i++){
System.out.println("线程B:" + i);
}
}
}
class Test2{
public static void main(String[] args) {
Runnable run1 = new MyThread3();
Thread thread = new Thread(run1);
thread.start();
Runnable run2 = new MyThread4();
Thread thread2 = new Thread(run2);
thread2.start();
}
}
结果显示:
线程B:411
线程B:412
线程B:413
线程B:414
线程A:469
线程A:470
线程A:471
线程A:472
线程A:473
线程A:474
public class MyThread3 implements Runnable {
@Override
public void run() {
for(int i = 0; i < 100; i++){
if(i % 2 == 0 ){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class Test2{
public static void main(String[] args) {
Runnable r1 = new MyThread3();
Thread t1 = new Thread(r1);
t1.setName("线程1");
t1.start(); //调用当前线程的run方法,实则调用了Runnable类型的target的run方法。Thread类有参构造参数Runnable target
//再启动一个线程,遍历100以内的偶数
Thread t2 = new Thread(r1);
t2.setName("线程2");
t2.start();
}
}
3. JDK5.0新增线程创建方式
3-1 实现Callable接口
-
创建实现Callable接口的实现类;
-
重写call方法,此方法有返回值,可抛出异常,无返回值的话,return null;
-
创建实现类对象;
-
将创建类对象作为参数传递到FutureTask构造器中,创建FutureTask对象;
-
将FutureTask对象作为参数传递到Thread类的构造器中,创建Thread类对象,并start()。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class NumberThread implements Callable{
@Override
public Object call() throws Exception {
int num = 0;
for(int i = 1; i <= 100; i++){
if(i % 2 == 0){
System.out.println(i);
num += i;
}
}
return num;
}
}
public class ThreadNew {
public static void main(String[] args) {
NumberThread n1 = new NumberThread();
FutureTask futureTask = new FutureTask(n1);
//使用的是Thread构造参数为Runnablel类型的参数,FutureTask类实现了Runnale,Future接口
//启动线程
new Thread(futureTask).start();
//下面只是为了获取总和值,可不要
Object sum = null;
try {
//get()方法返回值即为FutureTask构造参数Callable实现类重写的call()返回值
sum = futureTask.get();
System.out.println("总和为 " + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
3-2 使用线程池
真正在开发中不会自己创建一个个线程去做,而是通过线程池来解决
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。
JDK5.0起提供的线程池API,ExecutorService和Executors.
ExecutorService,真正的线程池接口。
Executors,线程池的工具类,用于创建并返回不同类型的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class NumThread implements Runnable{
@Override
public void run() {
for(int i = 0; i <= 100; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
}
}
class NumThread2 implements Runnable{
@Override
public void run() {
for(int i = 0; i <= 100; i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1.创建一个可重用的固定线程数的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
/*ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;
//设置线程池的属性
service1.setCorePoolSize(15);
service1.setKeepAliveTime();*/
//2.执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumThread()); //适合使用于Runnable
//service.submit(Callable callable); //适合使用于Callable
service.execute(new NumThread2());
//3.关闭线程池
service.shutdown(); //线程池不用了,可以关闭下
//1输出偶数,2输出奇数
}
}
好处:
- 提高响应速度,减少创建新线程的时间
- 降低资源消耗,重复利用线程池中线程,不需要每次都创建
- 便于线程管理,如corePoolSize,核心池大小;maximumPoolSize,最大线程数;keepAliveTime,线程没有任务时最多保持多长时间后终止
Thread类的方法
void run() | 重写此方法,表明线程在被调度时执行的操作 |
---|---|
void start() | 启动线程,调用线程的run方法 |
void SetName(String name) | 改变线程名称 |
String getName() | 返回线程名称 |
static Thread currentThread() | 返回当前代码线程 |
static void yield() | 暂停该线程的执行,交出当前CPU的执行权,可能还会是该线程 |
getPriority() | 获取线程优先级 |
setPriority(int newPriority) | 设置线程优先级,优先级取值范围为[1,10],默认5,在调用start()方法之前使用 |
getState() | 获取线程的状态 |
join() | 在线程a中,线程b调用join方法,线程a进入阻塞状态,知道线程b完全执行完成,线程a才结束阻塞状态,接着看CPU分配到资源后,继续执行 |
static void sleep(long mills) | 让当前线程休眠指定的毫秒数,在此时间内,当前线程处于阻塞状态,时间到,等着CPU分配资源,分配到即可执行 |
void stop() | 已过时,执行此方法时,强制结束当前线程 |
boolean isAlive() | 判断当前线程是否存活,线程run()执行完成,相当于线程死亡,结束 |
线程命名方法,一个是以上的get,set方法,一个是通过构造器设置
如下:
public class MyThread extends Thread{
@Override
public void run() {
for(int i = 0 ; i < 500; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": "+ i);
}
}
}
public MyThread(String name) {
super(name);
}
}
class Test{
public static void main(String[] args) {
MyThread m1 = new MyThread("线程A");
m1.start();
MyThread m2 = new MyThread("线程B");
m2.start();
for(int i = 0 ; i < 500; i++){
if(i % 2 == 0){
System.out.println("main:" + i);
}
}
}
}
public class MyThread extends Thread{
@Override
public void run() {
for(int i = 0 ; i < 500; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": "+ i);
}
if(i % 20 == 0){
yield();
}
}
}
public MyThread(String name) {
super(name);
}
}
class Test{
public static void main(String[] args) {
MyThread m1 = new MyThread("线程A");
m1.start();
MyThread m2 = new MyThread("线程B");
m2.start();
for(int i = 0 ; i < 500; i++){
if(i % 2 == 0){
System.out.println("main:" + i);
}
}
}
}
线程B: 238
线程B: 240
线程A: 244
线程A: 246
public class MyThread extends Thread{
@Override
public void run() {
for(int i = 0 ; i < 100; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": "+ i);
}
}
}
public MyThread(String name) {
super(name);
}
}
class Test{
public static void main(String[] args) {
MyThread m1 = new MyThread("Thread:1 ");
m1.start();
Thread.currentThread().setName("主线程");
for(int i = 0 ; i < 100; i++){
if(i % 2 == 0){
System.out.println( Thread.currentThread().getName() + ":" + i);
}
if(i == 20){
try {
m1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//当主线程执行到20时,通过join方法会发现,分线程Thread:1会执行完成,然后主线程接着执行
线程调度
多个线程被CPU执行时,调度策略是:CPU采用时间片方式,切换,执行一段这个线程,在执行一段另一个线程。还有方式是抢占式的,多个线程设置优先级,CPU在切换的时候优先考虑高优先级的线程
高优先级的线程要抢占低优先级线程CPU的执行权,只是从概率上说,高优先级的线程高概率情况被CPU执行,不意味着,只有当高优先级的线程执行完之后,低优先级线程才被执行,二者也是会交互的
售票
/**
* 三个窗口卖票,总票数为100张
*/
class Window extends Thread{
private int ticket = 100;
@Override
public void run() {
while (true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + ": 卖票,票号为:" + ticket);
ticket--;
}else{
break; //跳出当前循环结构
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
结果成了每个窗口各自售卖100张票了
将ticket变量声明为static,多个对象共用一个ticket数据。private static int ticket = 100;
class Window1 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + "卖票,票号为 :" + ticket);
ticket--;
}else{
break;
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
//创建一个window对象,自然相当于共享数据
Runnable w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
比较创建线程的方式
开发中,优先选择实现接口的方式。实现的方式没有类的单继承性的局限性,另外,该方式更适合处理多个线程共享数据的情况
二者联系,Thread类本身也实现了Runnable接口,两种方式都需要重写run方法,将线程要执行的代码逻辑声明在run方法中
线程生命周期
java.lang.Thread.State枚举类,定义了线程的几种状态
- NEW 新建
- RUNNABLE 可运行的
- BLOCKED 阻塞
- WAITING 等待
- TIMED_WAITING 指定等待时间
- TERMINATED 线程终止
新建:当Thread类或其子类对象被声明创建时,即为线程新建状态,此时还未执行start方法
就绪:当新建的线程调用了start方法后,将进入线程队列等待CPU时间片,此时已具备了运行条件,只是可能没分配到CPU资源
运行:当就绪的线程被调度并获取CPU资源时,便进入运行状态。
阻塞:在特殊情况下,被认为挂起,让出CPU的执行权,终止自己的执行,就进入阻塞状态
死亡:线程run方法执行完成,或被提前强制性终止stop(),或因异常结束,还没处理
线程同步
卖票过程中出现重票,错票问题。
窗口1卖票,票号为 :1
窗口3卖票,票号为 :0
窗口2卖票,票号为 :-1
出现原因:当某个线程操作车票的过程中,尚未操作完成,其他线程参与进来,也操作车票
解决方案:当一个线程a在操作共享数据ticket的时候,其他线程不能参与进来,直到线程a操作完ticket后,其他线程才可以操作此共享数据,即使线程a出现了阻塞,也不能被改变
在java中,通过同步机制解决线程的安全问题
同步的方式,解决了线程的安全问题。操作同步代码块时,只能有一个线程参与,其他线程等待,相当于一个单线程的过程,效率低些
方式一:
同步代码块
synchronized(同步监视器){
//需要被同步的代码 //操作共享数据的代码
}
共享数据,多个线程共同操作的变量 ,比如卖票例子中的ticket。共享数据不能包含代码多了(一是效率问题,而是题意不符,如将本例中的synchronized和while代码交互,成了一个窗口卖票),亦不能减少(可能操作同样不是线程安全)
同步监视器,即为锁。任何一个类的对象,都可以作为锁,多个线程必须共用一把锁
一段代码,希望一个线程进来执行,在执行过程中可能会阻塞,但不管什么情况,只要在大括号内,其他线程都要去等待,当原先线程执行完之后,其他线程看哪个抢到了CPU资源,才可执行大括号内代码,此时才算线程安全的
实现方式线程安全售票
class Window1 implements Runnable{
private int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while(true){
// synchronized (obj){
synchronized (this){
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,票号为 :" + ticket);
ticket--;
}else{
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
//创建一个window对象
Runnable w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
继承方式线程安全售票
class Window extends Thread{
private static int ticket = 100;
private static Object obj = new Object();
@Override
public void run() {
while (true){
//synchronized (obj){
synchronized (Window.class){ //类只会加载一次,意味着Window.class只有一个
//synchronized (this){ //错误,this代表当前类的对象
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 卖票,票号为:" + ticket);
ticket--;
}else{
break; //跳出当前循环结构
}
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
在实现Runnable接口创建多线程的方式中,可以考虑使用this充当同步监视器
在继承Thread类创建多线程的方式中,要慎用使用this充当监视器,可考虑使用当前类充当同步监视器
二者是否使用this,要具体分析this代表的对象是否唯一
方式二:
同步方法。操作共享数据的代码完整的声明在一个方法中,可以将此方法声明为同步的
class Window3 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(true) {
show();
}
}
//同步监视器是this
private synchronized void show(){
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,票号为 :" + ticket);
ticket--;
}
//保证同步方法外(上)可以是多个线程,但方法内部只能是一个线程,即为安全
}
}
public class WindowTest3 {
public static void main(String[] args) {
//创建一个window对象
Runnable w = new Window3();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
public class WindowTest4 {
public static void main(String[] args) {
Window4 w1 = new Window4();
Window4 w2 = new Window4();
Window4 w3 = new Window4();
w1.start();
w2.start();
w3.start();
}
}
class Window4 extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
//同步监视器:Window4.class
private static synchronized void show() {
//private synchronized void show() {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 卖票,票号为:" + ticket);
ticket--;
}
}
}
同步方法解决线程安全问题:
同步方法中仍涉及到监视器,只是不需要显式声明;非静态的同步方法中,监视器为this;静态的同步方法,同步监视器,是当前类的本身
方式三:
JDK5新特性,提供更强大的线程同步机制,显式定义同步锁实现同步。同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock是个接口,是控制多个线程对共享资源进行访问的工具
ReentrantLock类实现了Lock接口
import java.util.concurrent.locks.ReentrantLock;
class Window5 implements Runnable{
private int tickets = 100;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
//调用锁定方法lock()
lock.lock();
if(tickets > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + tickets);
tickets--;
}else {
break;
}
} finally {
//调用解锁方法unlock()
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Runnable w = new Window5();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
synchronized与Lock的异同?
相同点:二者都可用来解决线程安全问题
不同点:synchronized机制,在执行完相应的同步代码后,属于自动地释放同步监视器;而Lock需要手动的启动同步,以及结束同步
三种优先使用顺序:
Lock
同步代码块
同步方法
线程安全的懒汉式模式
//第一种方式:
/*public static Bank getInstance(){
synchronized (Bank.class) { //1-1
if(instance == null){
instance = new Bank();
}
return instance;
}
}*/
/*public static synchronized Bank getInstance(){
if(instance == null){
instance = new Bank();
}
return instance;
}//使用此方式带来大的CPU开销,效率低些*/
//第二种方式:
public static Bank getInstance(){
if(instance == null){ //1-2
synchronized (Bank.class) { //2
if(instance == null){
instance = new Bank();
}
}
}
return instance;
}
第一种方式:比如说三个线程代码执行到1-1位置处,假设1先进入同步代码块中,创建一个Bank对象,返回此对象。执行过程中,只有线程1进来,其他两个只能等待,1出来代码块后,其他俩线程进去一个判断不符合,直接返回结果,后来线程都是如此。效率稍低。第二种方式:首次都可到达1-2位置处,之后,上锁,某一线程进入执行,其他线程在锁外等待,进入判断语句,创建对象,出锁,返回对象。其他线程等待后进来发现instance不为null了,直接带着数据出来。而之后新来的线程就不需要等待了。效率稍高
线程死锁
不同线程分别占有对方需要的同步资源,还都不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁问题。
出现死锁后,不会出现异常,也不会出现提示,所有的线程都处于阻塞状态,无法继续进行下去
写程序时,要避免出现死锁问题
线程通信
举例,使用两个线程打印1-100,线程1,2交替打印
class Number implements Runnable{
private int number = 1;
@Override
public void run() {
while (true){
synchronized (this){
if(number < 100){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": " + number);
number++;
}else{
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
此种方式没有两个线程之间的交替打印
通信方法
wait():执行此方法,当前线程进入阻塞状态,并释放同步锁
notify():执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的那个
noifyAll():执行此方法,唤醒所有被wait的线程
注意事项:这三个方法必须使用在同步代码块或同步方法中;还有这些方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现IllegalMonitorStateException异常。
这写方法是定义在java.lang.Object类中的。
class Number implements Runnable{
private int number = 1;
@Override
public void run() {
while (true){
synchronized (this){
notify();
if(number <= 100){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": " + number);
number++;
try {
//使得调用wait方法的线程进入阻塞状态,同时释放锁
//如果本例中只有此方法,第一个线程输出后阻塞,第二个线程同样执行完后阻塞,结果只输出两句
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
sleep方法和wait方法异同?
相同:执行方法,都能使得当前线程进入阻塞状态
不同:声明位置不同,sleep方法是声明在Thread类中的,wait方法是声明在Object类中的;调用范围不同:wait方法必须在同步方法或同步方法中调用,而sleep方法的调用没有特殊要求;如果两个方法在同步代码或同步方法中使用的话,sleep不会释放锁,而wait会释放锁
通信应用:生产者/消费者问题
生产者将产品交给店员,消费者从店员处取走产品,店员一次只能持有固定商品数量的产品,如果生产者试图生产更多的商品,店员会叫生产者停一下,如果店中商品数量有空余,再通知生产者继续生产;如果店中没有商品了,会告诉消费者,等店中有商品再通知消费者取走商品
为了避免添加商品和消费商品同时对共享数据商品数量进行操作,需要处理线程安全问题,需要将addProduct()和consumeProduct()更改为同步方法
class Clerk{//店员
private int products = 0;
public synchronized void addProduct() {
if(products < 20){
products++;
System.out.println(Thread.currentThread().getName() + ":开始生产第" + products +"个商品");
notify(); //唤醒消费者
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void consumeProduct() {
if(products > 0){
System.out.println(Thread.currentThread().getName() + ":开始消费第 " + products +"个商品");
products--;
notify(); //唤醒生产者
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " :开始生产商品" );
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.addProduct();
}
}
}
class Consumer extends Thread{
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " :开始消费商品" );
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
p1.setName("生产者1");
Consumer c1 = new Consumer(clerk);
c1.setName("消费者1");
/* Consumer c2 = new Consumer(clerk);
c2.setName("消费者2");
c2.start();*/
p1.start();
c1.start();
}
}