Appearance
全局配置
Appearance
多线程是提升程序性能⾮常重要的⼀种⽅式,使⽤多线程可以让程序充分利⽤CPU资源,提⾼CPU的 使⽤效率,从⽽到达解决⾼并发所带来的负载均衡的问题。
1.CPU资源得到更合理的利⽤。 2.程序设计更加简洁。 3.程序响应更快,运⾏效率更⾼。 多线程的缺点: 1.需要更多的内存空间来⽀持多线程。 2.多线程并发访问的情况可能会影响数据的准确性。 3.⼀个资源被多个线程共享时,可能会出现死锁的情况。
计算机在某个数据集合上进⾏的⼀次运⾏活动,是系统进⾏资源分配和调度的基本单位。
简单理解,进程就是计算机正在运⾏的⼀个具体的应⽤程序,⼀个应⽤程序⾄少有⼀个进程,也可以 是多个进程。
线程是程序执⾏的最⼩单元,线程是进程中的⼀个基本单位,为独⽴完成程序中的某⼀个功能⽽存在 的,⼀个进程是由⼀个或多个线程组成的。 进程和线程是应⽤程序在执⾏过程中所产⽣的概念,即如果⼀个应⽤程序没有运⾏,就没有线程和进 程的概念,应⽤程序是⼀个静态概念,进程和线程是动态概念,只有当静态的应⽤程序运⾏起来,才 会产⽣对应的动态的进程和线程,有创建有销毁,存在也是暂时的,不可能永远存在。
进程在运⾏时拥有独⽴的内存空间,即每个进程所占⽤的内存都是独⽴的,互不⼲扰。⽽多线程是共 享内存空间的,线程不能独⽴执⾏,必须依赖与进程,由进程提供多线程的执⾏控制。 我们通常所说的多线程是指在⼀个进程中,多个线程同时执⾏,注意:这⾥的同时执⾏并不是真正意 义上的同时执⾏,系统会⾃动为每⼀个线程分配CPU资源,在某⼀个具体的时间段内CPU被⼀个线程 所占⽤,不同的时间段内不同的线程占⽤CPU资源,所以多线程实际上是多个线程在交替执⾏,CPU 运⾏速度很快,感觉上是同时在执⾏。
例1:单线程
public class Test {
//主线程
public static void main(String[] args) {
//主线程的业务逻辑
for (int i = 0; i < 100; i++) {
System.out.println("test+++++++++++++++");
}
Test2 test2 = new Test2();
test2.test();
// test+++++++++++++++
// ...
// -------------test
// ...
}
}
public class Test2 {
public void test() {
for (int i = 0; i < 100; i++) {
System.out.println("-------------test");
}
}
}
例2:多线程
package com.southwind.test;
public class Test {
//主线程
public static void main(String[] args) {
//开启第一个子线程
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
//子线程的业务逻辑
for (int i = 0; i < 100; i++) {
System.out.println("-----------test");
}
}
}).start();
//开启第二个子线程
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
//子线程的业务逻辑
for (int i = 0; i < 100; i++) {
System.out.println("========test==========");
}
}
}).start();
//主线程的业务逻辑
for (int i = 0; i < 100; i++) {
System.out.println("test+++++++++++++++");
}
}
}
1.⾃定义⼀个类,继承Thread类。 2.重写Thread类中的run⽅法,将相应的业务逻辑在run⽅法中实现。 定义好了⼀个线程类之后,我们就可以通过该类来实例化对象,对象就可以描述⼀个线程。 实例化该对象之后,必须通过调⽤start()来开启该线程,这样该线程才会和其他线程来抢占CPU资 源,不能调⽤run()⽅法,调⽤run()相当于普通的实例对象⽅法调⽤,并不是多线程。
public class MyThread extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0; i < 100; i++) {
System.out.println("MyThread+++++++++++++++");
}
}
}
public class Test3 {
//主线程
public static void main(String[] args) {
//创建一个MyThread线程,子线程
MyThread myThread = new MyThread();
//开启线程,使用start()方法,如果直接使用run()方法,如myThread.run()相当于普通的实例对象⽅法调⽤,并不是多线程。
myThread.start();
//主线程的业务逻辑
for(int i = 0; i < 100; i++) {
System.out.println("----------------Test3");
}
}
}
1.⾃定义⼀个类,实现Runnable接⼝。 2.实现run⽅法。 MyRunnable的使⽤与MyThread略有不同,MyRunnable相当于定义了线程的业务逻辑,但是 它本身不是⼀个线程,所以需要实例化Thread类的对象作为线程,然后将MyRunnable对象赋给 Thread对象,这样Thread就拥有了MyRunnable中定义的业务逻辑,再通过调⽤Thread对象的 start⽅法来启动线程。
public class MyRunnable implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
System.out.println("=========MyRunnable========");
}
}
}
public class Test4 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
//创建状态
Thread thread = new Thread(myRunnable);
//就绪状态
thread.start();
for(int i = 0 ;i < 100; i++) {
System.out.println("++++++++Test4");
}
}
}
线程⼀共有5种状态,在特定的情况下,线程可以在不同的状态之间进⾏切换,5种状态如下: 1.创建状态:实例化了⼀个新的线程对象,还未启动。 2.就绪状态:线程对象创建好之后,调⽤了该对象的start⽅法启动该线程,该状态下的线程位于可运 ⾏线程池中,等待系统为其分配CPU资源。 3.运⾏状态:就绪状态的线程在某个时间段内获取到了CPU资源,执⾏相应的业务逻辑(run⽅法的逻 辑)。 4.阻塞状态:运⾏状态的线程因某些原因暂时放弃CPU资源,停⽌执⾏程序,解除阻塞之后不能直接 回到运⾏状态,⽽是重新回到就绪状态,等待下⼀次的CPU资源。 5.死亡状态:线程执⾏完毕或因为异常退出,该线程的⽣命周期结束了。
1、线程休眠(sleep):让当前线程对象暂停执行,从运行状态进入阻塞状态,将 CPU资源让给其他线程的一种调度方式。 2、线程合并(join):线程甲在某个时刻调用了线程乙的join,从这一时刻开始, CPU资源完全被线程乙占用,直到线程乙的任务执行完毕,将CPU资源释放, 线程甲才能继续执行。 3、线程礼让(yield):让当前线程在某一个时刻让出CPU资源,仅仅是在这一时刻进行 礼让,在下一个时刻会继续争夺CPU资源。 4、线程中断(interrupt): 线程中断有三种方式: 1.线程的任务执行完毕,自动关闭。 2.线程执行过程中,因为抛出异常导致线程中断。 3.根据需求手动进行中断。
休眠指让当前线程暂停执行一段时间,从运行状态进入阻塞状态,将CPU资源让给其他线程的一种调度方法,通过sleep方法来实现,sleep(long millis)是java.lang.Thread类中定义的方法,使用时需要指定当前线程休眠的时间,单位为毫秒(1000毫秒=1秒)。
public class Test2 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
try {
myThread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
myThread.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 10; i++) {
if(i == 5) {
try {
sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(i);
}
}
}
mian线程进行休眠操作
public class Test3 {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
if(i == 5) {
try {
Thread.currentThread().sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(i);
}
}
}
合并的意思是将指定的某个线程合并到当前线程中,将原本两个交替执行的线程改为顺序执行,即一个线程执行完毕之后再来执行第二个线程,通过调用线程对象的join方法来实现合并。
具体如何来实现合并?谁为主谁为从?假设有两个线程:线程甲,线程乙。 线程甲在执行到某个时间点的时候调用了线程乙的join方法,则表示从当前时间点开始CPU资源被线程乙独占,线程甲进行阻塞状态,直到线程乙执行完毕,线程甲重新进入就绪状态,等待获取CPU资源进入运行状态继续执行。
package com.southwind.thread;
public class Test4 {
public static void main(String[] args) {
JoinRunnable joinRunnable = new JoinRunnable();
Thread thread = new Thread(joinRunnable);
thread.start();
for (int i = 0; i < 100; i++) {
if(i == 10) {
try {
// 如果传入时间表示该线程只占用传入时间长度的CPU
//thread.join(3000);
thread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(i+"Test+++++++++++++++++");
}
}
}
class JoinRunnable implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 10; i++) {
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(i+"------------JoinRunnable");
}
}
}
同样是完成线程合并的操作,join()和join(long millis)是有区别的: join()表示被调用线程执行完成之后才能释放CPU资源,让其他线程来执行, join(long millis)则表示被调用的线程执行millis毫秒之后,无论其是否执行完毕,其他线程都可以和它来争夺CPU资源。
线程礼让是指在某个特定的时间点,让当前线程暂停抢占CPU资源的行为,即从运行状态或就绪状态来到阻塞状态,从而将CPU资源让给其他线程来使用。 假如有线程甲和线程乙在交替执行,某个时间点线程甲作出了礼让,所以在这个时间点线程乙就拥有了CPU资源,执行其业务逻辑,但不是说线程甲会一直暂停争夺,线程甲只是在特定的时间节点进行礼让,一旦过了这个时间节点,线程甲再次进入就绪状态,和线程乙来争夺CPU资源。
通过调用线程对象的yield方法来完成礼让。
package com.southwind.thread;
public class Test5 {
public static void main(String[] args) {
YieldThread1 y1 = new YieldThread1();
y1.setName("YieldThread1");
YieldThread2 y2 = new YieldThread2();
y2.setName("YieldThread2");
y1.start();
y2.start();
}
}
class YieldThread1 extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 10; i++) {
if(i == 5) {
yield();
}
System.out.println(getName()+"------"+i);
}
}
}
class YieldThread2 extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 30; i++) {
System.out.println(getName()+"------"+i);
}
}
}
Java中实现线程中断机制有如下几个方法: public void stop() public void interrupt() public boolean isInterrupt()
stop()方法在新版本的JDK已经不推荐使用了,interrupt是一个实例方法,当一个线程对象调用该方法时,表示中断当前线程对象。
每一个线程都有一个标志位来标识当前线程对象是否为中断状态,isInterrupt()方法就是用来获取当前线程对象的标志位的,true表示清除了标志位,当前线程对象已经中断,false表示没有清除标志位,当前线程对象没有中断。
线程中断有三种方式: 1.线程的任务执行完毕,自动关闭。 2.线程执行过程中,因为抛出异常导致线程中断。 3.根据需求手动进行中断。
package com.southwind.thread;
public class Test6 {
public static void main(String[] args) {
Thread thread = new Thread();
System.out.println(thread.getState());
thread.start();
System.out.println(thread.isInterrupted());
thread.interrupt();
System.out.println(thread.isInterrupted());
}
}
Java中允许多线程并行访问时会产生数据安全问题,例如,当多个变量同时操作一个资源或者访问一个共享数据/变量时,就可能导致数据不准确或者产生错误。解决这一问题就需要使用线程同步synchronized。 使用synchronized关键字修饰方法来实现线程同步。 每个Java对象都有一个内置锁,内置锁会使用synchronized关键字修饰的方法,即要调用该方法,必须先获得内置锁,否则该线程就处于阻塞状态。
以下例子,有一定概率出现线程1和2都是"第2位访客"。
package com.southwind.thread;
public class Test {
public static void main(String[] args) {
Account account = new Account();
Thread thread1 = new Thread(account,"线程1");
Thread thread2 = new Thread(account,"线程2");
thread1.start();
thread2.start();
}
}
class Account implements Runnable{
private static int num;
@Override
public void run() {
// TODO Auto-generated method stub
num++;
try {
Thread.currentThread().sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"是当前第"+num+"位访客");
}
}
synchronized可以修饰实例方法,也可以修饰静态方法,还可以修饰代码块,会为代码块加上内置锁,从而实现线程同步。 在静态方法中添加同步代码块。 synchronized修饰同步代码块, synchronized(){ // ... }
public class Test {
public static void main(String[] args) {
Account account = new Account();
Thread thread1 = new Thread(account,"线程1");
Thread thread2 = new Thread(account,"线程2");
thread1.start();
thread2.start();
}
}
class Account implements Runnable{
// 资源需要共享(使用static)
private static int num;
@Override
// 访问共享资源的方法使用synchronized关键字加锁(修饰实例方法)
public synchronized void run() {
// TODO Auto-generated method stub
num++;
try {
Thread.currentThread().sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"是当前第"+num+"位访客");
}
}
public class SynchronizedTest {
private static Integer num = 1;
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
// 实例化匿名内部类new Runnable
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
test();
}
}).start();
}
}
// 使用synchronized修饰静态方法
public synchronized static void test() {
System.out.println("start...");
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("end...");
}
}
// start...
// end...
// start...
// end...
// start...
// end...
// start...
// end...
// start...
// end...
()内部需要设置加锁的资源,静态方法属于类的方法,不属于任何一个实例对象,所以静态方法中的synchronized()只能锁定类,不能锁定实例对象,this可以表示当前的一个实例。
注意,这里所说的的不能锁定实例变量是因为,相当于每个线程锁定了自己的实例变量,并看到变量同步的效果。事实上,synchronized()会调用equals方法比较锁定的值,除了相同变量会被锁定,不同的变量拥有相同值时也会被锁定。
package com.southwind.thread;
public class SynchronizedTest {
private static Integer num = 1;
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
test();
}
}).start();
}
}
public static void test() {
// synchronized修饰代码块,synchronized(锁定资源){}
synchronized (num) {
System.out.println("start...");
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("end...");
}
}
}
package com.southwind.thread;
public class SynchronizedTest2 {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
SynchronizedTest2 st = new SynchronizedTest2();
st.test();
}
}).start();
}
}
public void test() {
// 不能锁定实例对象或实例变量
synchronized (this) {
System.out.println("start...");
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("end...");
}
}
}
MyRunnable.class:获取的是MyRunnable在内存中的运行时类,每个运行时类只有一份。
整个系统中,无论做怎样的业务操作,保证内存中只有一个对象实例。
// 单线程的单例模式
class Singleton{
private volatile static Singleton instance = null;
private Singleton() {
System.out.println("创建了Singleton对象");
}
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
数据保存在主内存中的,线程在访问该数据时,并不是直接访问主内存中的数据,而是从主内存中拷贝一份数据出来放入工作内存,线程对工作内存中的数据进行操作,操作完成之后再将工作内存中的数据拷贝到主内存中。
使用volatile关键字来解决因为工作内存-主内存机制导致可能会发生的单例的错误。
使用volatile关键字修饰的变量,主内存对线程可见。
线程甲修改了其工作内存中的数据,线程乙工作内存中的数据会同步更新的。
// 多线程的单例模式
public class Test2 {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
Singleton singleton = Singleton.getInstance();
}
}).start();
}
}
}
class Singleton{
// 使用valatile关键字
private volatile static Singleton instance = null;
// 构造函数私有化,保证不能被外部创建,从而保证单例
private Singleton() {
System.out.println("创建了Singleton对象");
}
// 公有接口,可以让外部取得该实例对象
public static Singleton getInstance() {
// double-check
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
public class Test3 {
public static void main(String[] args) {
DeadLockRunnable d1 = new DeadLockRunnable();
d1.flag = 1;
DeadLockRunnable d2 = new DeadLockRunnable();
d2.flag = 2;
new Thread(d1,"张三").start();
//避免出现死锁
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
new Thread(d2,"李四").start();
}
}
class DeadLockRunnable implements Runnable{
public int flag;
// 一个线程必须同时拥有两个资源才能进行
private static Object o1 = new Object();
private static Object o2 = new Object();
@Override
public void run() {
// TODO Auto-generated method stub
if(flag == 1) {
System.out.println(Thread.currentThread().getName()+"获取了资源o1,等待获取资源o2");
synchronized (o1) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (o2) {
System.out.println(Thread.currentThread().getName()+"执行完毕");
}
}
}
if(flag == 2) {
System.out.println(Thread.currentThread().getName()+"获取了资源o2,等待获取资源o1");
synchronized (o2) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (o1) {
System.out.println(Thread.currentThread().getName()+"执行完毕");
}
}
}
}
}
1.多线程模拟两个跑步,每个线程代表一个人,可设置每个人跑步的速度,每跑完100米给出响应的信息,跑到终点之后给出响应的提示。
public class Test4 {
public static void main(String[] args) {
RunRunnable r1 = new RunRunnable(4000, 1);
RunRunnable r2 = new RunRunnable(1000, 1);
new Thread(r1,"张三").start();
new Thread(r2,"李四").start();
}
}
class RunRunnable implements Runnable{
//跑100米需要的时间
private int time;
//已跑完的100米
private int num;
public RunRunnable(int time,int km) {
this.time = time;
this.num = km*1000/100;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(num > 0) {
System.out.println(Thread.currentThread().getName()+"跑了100米!");
try {
Thread.currentThread().sleep(this.time);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
num--;
}
System.out.println(Thread.currentThread().getName()+"到达了终点!");
}
}
2.某楼盘摇号买房,分为普通号和VIP号,50个普通号,10个VIP号。VIP号的选房时间为普通号的2倍,开始普通号和VIP号并行叫号,叫到VIP号的概率比普通号更高,当普通号叫完第10号时,要求先让VIP号全部选完,再让普通号选房,用多线程模拟这个过程。
public class Test {
public static void main(String[] args) {
//VIP选房的线程
VIPThread vip = new VIPThread();
vip.setPriority(Thread.MAX_PRIORITY);
vip.start();
//普通号选房的线程
NormalRunnable normal = new NormalRunnable();
normal.setThread(vip);
Thread thread2 = new Thread(normal);
// priority为[1,10]之间的整数。NORM_PRIORITY为5,MIN_PRIORITY为1,为10.
thread2.setPriority(Thread.MIN_PRIORITY);
thread2.start();
}
}
class VIPThread extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 1; i <= 10; i++) {
System.out.println("VIP"+i+"正在选房");
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class NormalRunnable implements Runnable{
private Thread thread;
public Thread getThread() {
return thread;
}
public void setThread(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 1; i <= 50; i++) {
if(i == 10) {
try {
thread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("普通号"+i+"正在选房");
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
3.用多线程模拟网络购票,“游客”,“学生”,“代理”共同抢15张票。
public class Test2 {
public static void main(String[] args) {
TicketRunnable ticketRunnable = new TicketRunnable();
Thread t1 = new Thread(ticketRunnable,"游客");
Thread t2 = new Thread(ticketRunnable,"学生");
Thread t3 = new Thread(ticketRunnable,"代理");
t1.start();
t2.start();
t3.start();
}
}
class TicketRunnable implements Runnable{
//剩余票数
public int count = 15;
//已售出的票数
public int num = 0;
@Override
// 法1:public synchronized void run()
public void run() {
// TODO Auto-generated method stub
while(count > 0) {
try {
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 法2:
synchronized (TicketRunnable.class) {
if(count == 0) {
return;
}
count--;
num++;
System.out.println(Thread.currentThread().getName()+"抢到了第"+num+"张票,还剩"+count+"张票");
}
}
}
}
wait的功能和sleep类似,都是让线程暂停执行任务,但是其实是两个完全不同的方法。 sleep是Thread类中的方法,让当前线程实例对象暂停执行任务,进入阻塞状态。 wait是Object类的方法,所以它不是针对线程对象的方法,而是针对线程对象要访问的资源对象的方法。
即调用A对象的wait方法表示:让当前正在访问A对象的线程暂停,同时它有一个前提,当前线程对象必须拥有A对象,所以wait方法只能在同步方法或同步代码块中使用,否则会抛出IllegalMonitorStateException异常。
package com.southwind.thread;
public class Test4 {
public static void main(String[] args) {
A a = new A();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 10; i++) {
a.test(i);
}
}
}).start();
}
}
class A {
public synchronized void test(int i) {
if(i == 5) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(i+"---A");
}
}
如何解除wait造成的阻塞? 1.指定wait时间,调用wait(long millis)即可,millis毫米之后会自动解除阻塞,和sleep(long millis)类似的方法。 2.notify
public class Test4 {
public static void main(String[] args) {
A a = new A();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 10; i++) {
a.test(i);
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.currentThread().sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
a.test2();
}
}).start();
}
}
class A {
public synchronized void test(int i) {
if(i == 5) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(i+"---A");
}
public synchronized void test2() {
// 把当前正在能够访问A对象的休眠线程唤醒
this.notify();
}
}
public class Hamburger {
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Hamburger(int id) {
this.id = id;
}
@Override
public String toString() {
return "Hamburger [id=" + id + "]";
}
}
/*
* 栈:后进先出
*/
public class SyncStack {
public Hamburger[] array = new Hamburger[6];
public int index = 0;
/*
* 向容器中添加汉堡
*/
public synchronized void push(Hamburger hamburger) {
while(index == array.length) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.notify();
array[index] = hamburger;
index++;
System.out.println("生产了一个汉堡:"+hamburger);
}
/*
* 从容器中取出汉堡
*/
public synchronized Hamburger pop() {
while(index == 0) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.notify();
index--;
System.out.println("消费了一个汉堡:"+array[index]);
return array[index];
}
}
// 生产者
public class Produce implements Runnable{
private SyncStack syncStack = null;
public Produce(SyncStack syncStack) {
// TODO Auto-generated constructor stub
this.syncStack = syncStack;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 20; i++) {
Hamburger hamburger = new Hamburger(i);
this.syncStack.push(hamburger);
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
// 消费者
public class Consumer implements Runnable{
private SyncStack syncStack;
public Consumer(SyncStack syncStack) {
this.syncStack = syncStack;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 20; i++) {
this.syncStack.pop();
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class Test {
public static void main(String[] args) {
SyncStack syncStack = new SyncStack();
Produce produce = new Produce(syncStack);
Consumer consumer = new Consumer(syncStack);
new Thread(produce).start();
new Thread(produce).start();
new Thread(produce).start();
new Thread(consumer).start();
new Thread(consumer).start();
}
}
ReentrantLock,是对synchronized的升级,synchronized是通过JVM实现的,ReentrantLock是通过JDK实现的。
重入锁指可以给同一个资源添加多个锁,并且解锁的方式与synchronized也不同,synchronized的锁是当线程执行完业务逻辑之后自动释放,ReentrantLock的锁必须手动释放。
1.可重入,可以给同一个资源同时添加多把锁,对应的解锁的次数必须与上锁的次数相同,否则就会出现程序不继续执行的情况。
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args) {
Account account = new Account();
new Thread(account).start();
new Thread(account).start();
}
}
class Account implements Runnable{
private int num;
private ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
// TODO Auto-generated method stub
reentrantLock.lock();
reentrantLock.lock();
num++;
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"是第"+num+"位访问");
reentrantLock.unlock();
reentrantLock.unlock();
}
}
2.可中断,指某个线程在等待获取锁的过程中可主动终止线程。
import java.util.concurrent.locks.ReentrantLock;
public class Test2 {
public static void main(String[] args) {
StopLock stopLock = new StopLock();
Thread t1 = new Thread(stopLock,"线程1");
Thread t2 = new Thread(stopLock,"线程2");
t1.start();
t2.start();
try {
Thread.currentThread().sleep(6000);
t2.interrupt();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class StopLock implements Runnable{
private ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
// TODO Auto-generated method stub
try {
reentrantLock.lockInterruptibly();
System.out.println(Thread.currentThread().getName()+"get lock");
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
reentrantLock.unlock();
}
}
}
3.限时性,指可以判断某个线程在一定的时间段内能否获取锁,通过boolean tryLock(long time,TimeUnit unit),time表示时间数值,unit表示时间单位,返回值为boolean类型,true表示在该时间段内获取了锁,false表示在该时间段内没有获取锁。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class Test3 {
public static void main(String[] args) {
TimeLock timeLock = new TimeLock();
new Thread(timeLock).start();
new Thread(timeLock).start();
}
}
class TimeLock implements Runnable{
private ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
// TODO Auto-generated method stub
try {
if(reentrantLock.tryLock(6, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName()+"get lock");
Thread.currentThread().sleep(5000);
}else {
System.out.println(Thread.currentThread().getName()+"not get lock");
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
// 判断当前线程是否已经被上锁
if(reentrantLock.isHeldByCurrentThread()) {
reentrantLock.unlock();
}
}
}
}
并行和并发。
并行:指多个操作同时执行,判断程序是否处于并行状态,就看同一个时刻是否有一个以上的工作单元在运行,单线程是永远无法达到并行状态的。 并发:指的是程序的结构,处理并行的能力。如果一个系统称之为并发系统,则表示该系统采用了支持并发的设计模式,所以并发并不是指多个线程同时执行,它指的是一种人为设计的程序结构,可以处理多线程的能力。
高并发是指我们设计的程序,可以支持海量的任务在同一时间段内同时执行。
高并发的标准: 1.QPS:每秒响应的http请求数量,QPS不等于并发量,并发数是指某时刻同时到到达服务器的请求数量。 2.吞吐量:单位时间内可以处理的请求数。 3.平均响应时间:系统对一个请求做出响应的平均时间。 QPS = 并发数/平均响应时间。 4.并发用户数量:系统在正常运行情况可以承载的用户数量。
提供系统并发能力的两种方式:1.垂直扩展,水平扩展。
提升单机的处理能力。 1.增强单机硬件性能。 2.提升单机架构性能。
系统集群(分层架构,nginx反向代理分担web应用服务器的压力,数据层主从复制,读写分离,分表分库,减轻数据库服务器的压力)
当提交一个任务时,线程池会创建一个新的线程去执行该任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务会被存入阻塞队列,等待被执行;如果阻塞队列满了,那就创建一个新的线程来执行当前任务,直到线程池中的线程数等于maxmumPoolSize,这时如果再有任务提交过来,只能执行reject()方法来拒绝处理。
优点: 1.线程的创建和销毁是需要耗费资源的,使用线程池可以有效的减少创建和销毁线程的次数,每个工作线程都可以重复使用。 2.可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多内存导致服务器崩溃。
工作流程: (1)当提交一个任务时,线程池会创建一个新的线程执行任务,直到当前线程池中的线程数等于corePoolSize:线程池的大小。 (2)如果当前线程数量已经到达了corePoolSize,继续提交的任务被保存到阻塞队列中,等待获取执行完成的任务释放线程,从而执行等待任务。 (3)如果阻塞队列满了,那就再创建新的线程去执行任务,直到线程池中的线程数达到maximumPoolSize,这时候如果再有任务来,只能执行拒绝方案。
java.util.concurrent.ThreadPoolExecutor类是Java描述线程池中最核心的一个类。
核心参数:
corePoolSize:核心池的大小。 ThreadPoolExecutor提供了动态修改线程池容量的方法: setCorePoolSize()修改corePoolSize setMaxmumPoolSize()修改maxmumPoolSize maxmumPoolSize:线程池的最大线程数,线程池中线程数量的上限。 keepAliveTime:空闲的线程可以存活的时间,线程池中的线程数大于corePoolSize,线程池进入了紧急预案状态,当线程数小于corePoolSize,keepAliveTime不起作用。例如,corePoolSize为5,keepAliveTime为10,线程池中有6个线程,如果某个线程的闲置时间达到了10s,则释放该线程。直到线程池中的线程数量小于等于corePoolSize,keepAliveTime失效。 unit:keepAliveTime参数的时间单位,TimeUnit中有7个常量来表示7种不同的时间单位。 TimeUnit.DAYS 天 TimeUnit.HOURS 小时 TimeUnit.MINUTES 分钟 TimeUnit.SECONDS 秒 TimeUnit.MILLSECONDS 毫秒 TimeUnit.MICROSECONDS 微秒 TimeUnit.NANOSECONDS 纳秒 workQueue:一个阻塞队列,用来存储等待执行的任务,一般阻塞队列有以下几种选择:
threadFactory:线程工厂,用来创建线程。 handler:表示拒绝处理任务所采取的策略,有以下四种取值。
1.RUNNING:该状态下的线程池可以接收新的任务,并且处理阻塞队列中的任务。 2.SHUTDOWN:该状态下的线程池不接收新的任务,但是会处理阻塞队列中的任务。 3.STOP:该状态下的线程池不接收新的任务,也不会处理阻塞队列中的任务,并且会中断正在运行的任务。(暂停) 4.TIDYING:该状态表示线程池对线程进行整理优化。 5.TERMINATED:该状态表示线程池停止工作。(关闭)
ThreadPoolExecutor 继承了 AbstractExecutorService AbstractExecutorService是一个抽象类,实现了ExecutorService接口 ExecutorService继承了Executor
execute内部实现: 1.通过workCountof()方法获取当前线程池中的线程数。 如果小于corePoolSize,就通过addWorker()创建线程并执行该任务,否则,将该任务放入阻塞队列。
2.如果能够成功的放入到阻塞队列中,若当前线程池是非RUNNING状态,则将该任务从阻塞队列中移除,然后执行reject()拒绝处理该任务。 如果当前线程池处于RUNNING状态,则需要再次检查线程池,如果有空闲的线程则执行该任务。
3.如果不能将任务放入阻塞队列中,说明阻塞队列已满,就通过addWorker()方法尝试创建新的线程来执行该任务,如果addWorker()方法失败,则表示当前线程池中的线程数已经达到了maxmumPoolSize,执行reject()拒绝处理该任务。
submit内部实现: 会将提交的任务封装成一个FutureTask对象,FutureTask类实现了Runnable接口,这样就可以通过Executor.execute()提交FutureTask到线程池中等待被执行,最终执行的是FutureTask的run方法。
1.创建一个固定大小的线程池,任务超过10个时,将任务放入阻塞队列中。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test2 {
public static void main(String[] args) {
// 实际开发中,不提倡直接实例化ThreadPoolExecutor,而是通过Executors类提供的静态方法来创建线程池。
ExecutorService service = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
MyTask2 task = new MyTask2();
service.execute(task);
}
service.shutdown();
}
}
class MyTask2 implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("...");
}
}
2.创建一个缓存线程池。 newCachedThreadPool(),线程数可以达到Integer.MAX_VALUE,2147483647,内部使用SynchronousQueue作为阻塞队列。
ExecutorService service = Executors.newCachedThreadPool();
3.创建单例线程池。 newSingleThreadExecutor(),线程池中只有一个线程。
public class Test3 {
public static void main(String[] args) {
ExecutorService sevice = Executors.newSingleThreadExecutor();
for(int i = 0; i < 10; i++) {
MyTask2 task = new MyTask2();
sevice.execute(task);
}
sevice.shutdown();
}
}
4.任务调度线程池。 newScheduledThreadPool()
public class Test4 {
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
//延迟5秒执行,每隔3秒执行一次任务(任务,延迟时间,执行周期,单位)
service.scheduleAtFixedRate(new MyTask2(), 5, 3, TimeUnit.SECONDS);
service.shutdown();
}
}