一、线程安全
1、基本概念
(1)线程安全
如果有多个线程同时运行同一段逻辑处理代码,如果每次运行的结果与单线程时候运行的结果是一样的,就说他是线程安全的。
(2)并行
在同一时间点同时运行,强调的是时间点,是真正的同时运行。
(3)并发
同一时间段内运行同一段逻辑,强调的是时间段,没有真正运行。
(4)同步
用于确保临界资源一次只能被一个线程使用,用来保证数据的准确性。
(5)异步
线程之间互不干扰,各自运行,缺点是会造成临界资源数据不安全
(6)临界资源
多线程共享的示例变量,或者多线程共享的公共静态变量
二、关键字:synchronized(锁)
1、格式
synchronized( 任意对象 ){
需要枷锁的代码块;
}
类锁与对象锁互不干扰
2、分类
(1) 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{ }括起来的代码,作用的对象是调用这个代码块的对象;
synchronized (obj) {
System.out.println( Thread.currentThread( ).getName( ) + i );
}
(2)修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
public synchronized void demo1( ){
方法的执行内容;
}
其等价于:
public void demo2( ) {
synchronized( this ) {
方法执行内容;
}
}
(3)修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
public static synchronized void demo4( ) {
方法执行内容;
}
(4)修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象。
public void demo2( ) {
synchronized( this ) {
方法执行内容;
}
}
其与第三种效果相同。
(5)synchronized锁Demo
public class SnycDemo {
public Object obj = new Object( );
//锁代码块
public void demo0( ) {
try {
for( int i = 0 ; i < 100 ; i ++ ) {
synchronized (obj) {
System.out.println( Thread.currentThread( ).getName( ) + i );
}
}
} catch (Exception e) {
e.printStackTrace( );
}
}
//对象锁
public synchronized void demo1( ) {
try {
for( int i = 0 ; i < 100 ; i ++ ) {
System.out.println( Thread.currentThread( ).getName( ) + i );
}
} catch (Exception e) {
e.printStackTrace( );
}
}
//对象锁
public void demo2( ) {
synchronized( this ) {
try {
for( int i = 0 ; i < 100 ; i ++ ) {
System.out.println( Thread.currentThread( ).getName( ) + i );
}
} catch (Exception e) {
e.printStackTrace( );
}
}
}
//类锁
public void demo3( ) {
synchronized( SnycDemo.class) {
try {
for( int i = 0 ; i < 100 ; i ++ ) {
System.out.println( Thread.currentThread( ).getName( ) + i );
}
} catch (Exception e) {
e.printStackTrace( );
}
}
}
//类锁
public static synchronized void demo4( ) {
try {
for( int i = 0 ; i < 100 ; i ++ ) {
System.out.println( Thread.currentThread( ).getName( ) + i );
}
} catch (Exception e) {
e.printStackTrace( );
}
}
}
三、死锁
1、死锁产生条件
(1)互斥使用:当一个对象占有一个资源时,另外一个不能拥有;
(2)不可抢占:资源请求者不能强制从资源占有者手中抢夺资源,资源只能由占有者主动释放;
(3)请求和保持:资源请求者在请求其他资源时,保持对现有资源的占有;
(4)循环等待:存在一个等待队列,如p1占有p2的资源,p2占有p1的资源。
(5)死锁Demo
public class DeadSync {
//死锁
public static String left = "资源1";
public static String right = "资源2";
public static void main(String[] args) {
new Thread( "用户1" ) {
public void run( ) {
synchronized (left) {
try {
System.out.println( Thread.currentThread( ).getName( ) + "抢夺到资源1" );
Thread.sleep(1000);
System.out.println( Thread.currentThread( ).getName( ) + "开始抢夺到资源2" );
synchronized (right) {
System.out.println( Thread.currentThread( ).getName( ) + "抢夺到资源2" );
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start( );
new Thread( "用户2" ) {
public void run( ) {
synchronized (right) {
try {
System.out.println( Thread.currentThread( ).getName( ) + "抢夺到资源2" );
Thread.sleep(1000);
System.out.println( Thread.currentThread( ).getName( ) + "开始抢夺到资源1" );
synchronized (left) {
System.out.println( Thread.currentThread( ).getName( ) + "抢夺到资源1" );
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start( );
}
}
上面Demo2的运行结果为
用户1抢夺到资源1
用户2抢夺到资源2
用户1开始抢夺到资源2
用户2开始抢夺到资源1
用户1和用户2分别抢占资源1和资源2,一开始两个用户各自抢到一个资源,当开始抢另一个资源的时候,由于加了锁,双方都会对等待对方交出“锁的钥匙”,即占有权,并且都不会放弃现有资源的占有权,所以会出现程序没有结束,但也不能往下运行的结果,这就叫出现死锁。
我们在编写程序的时候,应尽量避免出现这种情况。
四、线程通信
1、概念
多个线程在处理同一个资源,但是处理的动作却不相同,通过一定的手段, 使各个线程能够有效地利用资源,这种手段就叫做线程间的通信(等待唤醒机制)。
2、方法(属于object类)
- wait( )方法 等,无线等;
- notify( )方法 唤醒:唤醒被wait( )的线程,只随机唤醒一个;
- notifAll( )方法 唤醒所有被wait( )的线程。唤醒:让线程具备执行的资格,能够被CPU调用。
3、wait( )和sleep( )区别
- wait( )属于Object类,sleep( )属于Thread类;
- wait( )在睡眠时会释放锁资源,sleep( )不会释放锁资源;
- wait( )只能在同步代码块使用,sleep( )可以在任何地方使用;
- sleep( )必须捕获异常try…catch,而wait( )不需要;
- sleep( )如果不捕获异常,线程自动死亡,wait( )抛出异常后线程会继续运行。
4、方法使用注意事项
- 必须在同步代码块中才有效;
- 使用这些方法时,必须标明所属锁;
五、线程池
1、概念
- 线程池就是首先创建一些线程,它们的集合称为线程池。
- 使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程, 程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务, 执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
2、作用
(1)避免重复创建及销毁线程
(2)达到线程的重用
线程池的返回值 ExecutorService 是Java提供的用于管理线程池的类,该类的两个作用:控制线程数量和重用线程
3、常用线程池
(1)可缓存线程池: Executors.newCacheThreadPool( )
先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务
线程池为无限大,当执行当前任务时上一个任务已经完成,会复用执行上一个任务的线程,而不用每次新建线程
(2)可重用固定个数的线程池: Executors.newFixedThreadPool(int n)
创建一个可重用固定个数的线程池,以共享的无界队列方式来运行这些线程。定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()
(3)定长线程池: Executors.newScheduledThreadPool(int n)
创建一个定长线程池,支持定时及周期性任务执行
(4)单线程化的线程池: Executors.newSingleThreadExecutor( )
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
六、线程示例
1、Demo1:有一个水池容量100L,编写两个线程(进水口、出水口),要求两个线程不能同时运行,充水完成立马放水,放水完成立马充水,充水速度5L/s,放水速度2L/s。
定义一个Flag类,里面有一个boolean类型的变量flag,用于判断充水或放水是否完成;
public class Flag {
public boolean flag;
}
定义两个线程(进水口waterIn和出水口waterOut)
进水口类
public class WaterIn extends Thread{
Flag f = new Flag( );
public WaterIn( ) { }
public WaterIn( Flag f ) {
this.f = f;
}
//重写run()方法
public void run( ) {
while( true ) {
synchronized ( f ) {
if( f.flag ) {
try {
f.wait( );
} catch (InterruptedException e) {
e.printStackTrace( );
}
} else {
f.notify( );
for ( int i = 0 ; i <= 20 ; i++) {
try {
Thread.sleep(100);
System.out.println( "充水" + 5*i + "L" );
f.flag = true;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println( "充水完成" );
}
}
}
}
}
出水口类
public class WaterOut extends Thread{
Flag f = new Flag( );
public WaterOut( ) { }
public WaterOut(Flag f) {
this.f = f;
}
//重写run()方法
public void run( ) {
while( true ) {
synchronized ( f ) {
if( f.flag ) {
f.notify( );
for( int i = 50 ; i >= 0 ; i -- ) {
try {
Thread.sleep(100);
System.out.println( "放水" + ( 100 - 2*i ) + "L");
f.flag = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println( "放水完成");
}else {
try {
f.wait( );
} catch (InterruptedException e) {
e.printStackTrace( );
}
}
}
}
}
}
主函数类,用于调用方法,实现功能。
public class Demo1 {
public static void main(String[] args) {
Flag f = new Flag();
WaterIn wi = new WaterIn(f);
WaterOut wo = new WaterOut(f);
wi.start();
wo.start();
}
}
2、Demo2:按照规律"12a34b56c…5152z"打印数字和字母的组合。
① 定义一个Flag类,里面有一个boolean类型的变量flag,用于判断充水或放水是否完成;
public class Flag {
public boolean flag;
}
打印数字的类
public class PrintNum extends Thread {
Flag f = new Flag( );
public PrintNum( ) {}
public PrintNum(Flag f) {
this.f = f;
}
public void run( ) {
synchronized ( f ) {
for ( int i = 1 ; i <= 52 ; i++) {
if( f.flag ) {
try {
f.wait( );
} catch (InterruptedException e) {
e.printStackTrace( );
}
} else {
f.notify( );
System.out.print( i );
System.out.print( i + 1 );
f.flag = true;
}
}
}
}
}
打印字母类
public class PrintChar extends Thread {
Flag f = new Flag( );
public PrintChar() { }
public PrintChar(Flag f) {
this.f = f;
}
public void run( ) {
synchronized ( f ) {
for( char i = 'a' ; i <= 'z' ; i ++ ) {
if( f.flag ) {
f.notify( );
System.out.print( i );
f.flag = false;
}else {
try {
i--;
f.wait( );
} catch (InterruptedException e) {
e.printStackTrace( );
}
}
}
}
}
}
main函数类
public class Demo2{
public static void main(String[] args) {
Flag f = new Flag();
PrintNum pn = new PrintNum(f);
PrintChar pc = new PrintChar(f);
pn.start();
pc.start();
}
}
3、Demo3:窗口售票
① 售票类
public class Tickets {
public Object obj = new Object( );
int total = 1;
public void saleTickets( ) {
while( total <= 10) {
synchronized( obj ) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if ( total > 10) {
System.out.println( "票卖完了" );
break;
}
System.out.println( Thread.currentThread( ).getName( ) + "卖出第" + total + "张票" );
total++;
}
}
}
}
main函数类
public class Demo3{
//卖票
public static void main(String[] args) {
Tickets tickets = new Tickets( );
new Thread( "窗口1" ) {
public void run( ) {
tickets.saleTickets( );
}
}.start( );
new Thread( "窗口2" ) {
public void run( ) {
tickets.saleTickets( );
}
}.start( );
}
}
4、Demo4:抽奖箱
public class Demo4{
//抽奖箱 做法2
public static void main(String[] args) {
Thread t1 = new Thread( "抽奖箱1" ) {
public void run( ) {
for( int i = 0 ; i < 100 ; i++ ) {
try {
Thread.sleep(100);
GetMoney.getMoney( );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t2 = new Thread( "抽奖箱2" ) {
public void run( ) {
for( int i = 0 ; i < 100 ; i ++ ) {
try {
Thread.sleep(100);
GetMoney.getMoney( );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start( );
t2.start( );
}
}
5、Demo5:实现三个线程,一个线程打印a, 一个打印b,一个打印c,三个线程同时执行,要求打印出6个的abc
public class Demo5{
static int num = 1;
public static void main(String[] args) {
Object obj = new Object();
for(int i = 0; i < 6; i++) {
new Thread() {
@Override
public void run() {
synchronized (obj) {
while(num != 1) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print( 'a');
num = 2;
obj.notifyAll();
}
}
}.start();
new Thread() {
@Override
public void run() {
synchronized (obj) {
while(num != 2) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print('b');
num = 3;
obj.notifyAll();
}
}
}.start();
new Thread() {
@Override
public void run() {
synchronized (obj) {
while(num != 3) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println('c');
num = 1;
obj.notifyAll();
}
}
}.start();
}
}
}
6、Demo6:某公司组织年会,会议入场时有两个入口,在入场时每位员工都能获取一张双色球彩票,假设公司有100个员工,利用多线程模拟年会入场过程,并分别统计每个入口入场的人数,以及每个员工拿到的彩票的号码。线程运行后打印格式如下:
编号为: 2 的员工 从后门 入场! 拿到的双色球彩票号码是: [17, 24, 29, 30, 31, 32, 07]
编号为: 1 的员工 从后门 入场! 拿到的双色球彩票号码是: [06, 11, 14, 22, 29, 32, 15]
…
从后门入场的员工总共: 13 位员工
从前门入场的员工总共: 87 位员工
线程类:
public class ColorBall {
public Object obj = new Object( );
int total = 1;
int a = 0;
int b = 0;
public void party( ) {
while( total <= 100 ) {
int[] arr = new int[7];
arr[6] = (int)( 16 * ( Math.random( ) ) + 1 );
for( int i = 0 ; i < 6 ; i++ ) {
arr[i]= (int)( 33 * ( Math.random( ) ) + 1 );
for( int j = 0 ; j < i ; j++ ){
if ( arr[i] == arr[j] ) {
--i;
continue;
}
}
}
String str = "[" + arr[0] + "," + arr[1] + "," + arr[2] + "," + arr[3] + "," + arr[4] + "," + arr[5] + "," + arr[6] + "]";
//员工入场
synchronized (obj) {
try {
Thread.sleep(10);
if( total <= 100 ){
//统计前后门进场员工人数
if ( Thread.currentThread( ).getName( ).equals( "前门" ) ) {
a++;
} else if (Thread.currentThread( ).getName( ).equals( "后门" ) ) {
b++;
}
System.out.println( "编号为:" + total + "的员工从" + Thread.currentThread( ).getName( ) + "入场,拿到的双色球彩票号码是:" + str );
total++;
} else {
System.out.println( "从前门入场的员工总共:" + a + "个员工");
System.out.println( "从后门入场的员工总共:" + b + "个员工");
}
} catch (InterruptedException e) {
e.printStackTrace( );
}
}
}
}
}
main函数类
public class Dem6 {
public static void main(String[] args) {
ColorBall cb = new ColorBall( );
new Thread( "前门" ) {
public void run( ) {
cb.party( );
}
}.start( );
new Thread( "后门" ) {
public void run( ) {
cb.party( );
}
}.start( );
}
}