目录
6.2在实例方法上使用synchorized和在静态方法上使用synchronized
一:多线程的基本概念
- 每个进程是一个应用程序,都有独立的内存空间 ,每个进程都相当于一个独立的APP
1.1什么是进程?
1.2什么是线程?
1.3多进程有什么作用?
1.4多线程有什么作用?
1.5java 程序的运行原理?
二:线程的创建和启动
2.1继承Thread类
/*
在java语言中实现多线程的第一种方式:
第一步:继承java.lang.Thread;
第二步:重写run方法.
三个知识点:
如何定义线程?
如何创建线程?
如何启动线程?
*/
public class ThreadTest02
{
public static void main(String[] args){
//创建线程
Thread t = new Processor();
//启动
t.start(); //这段代码执行瞬间结束。告诉JVM再分配一个新的栈给t线程.
//run不需要程序员手动调用,系统线程启动之后自动调用run方法.
//t.run(); //这是普通方法调用,这样做程序只有一个线程,run方法结束之后,下面程序才能继续执行。
//这段代码在主线程中运行.
for(int i=0;i<10;i++){
System.out.println("main-->" + i);
}
//有了多线程之后,main方法结束只是主线程栈中没有方法栈帧了。
//但是其他线程或者其他栈中还有栈帧。
//main方法结束,程序可能还在运行。
}
}
//定义一个线程
class Processor extends Thread
{
//重写run方法
public void run(){
for(int i=0;i<30;i++){
System.out.println("run-->" + i);
}
}
}
2.2实现Runable接口
/*
java中实现线程的第二种方式:
第一步:写一个类实现java.lang.Runnable;接口
第二步:实现run方法.
*/
public class ThreadTest03
{
public static void main(String[] args){
//创建线程
Thread t = new Thread(new Processor1());
//启动
t.start();
}
}
//这种方式是推荐的。因为一个类实现接口之外保留了类的继承。
class Processor1 implements Runnable {
public void run(){
for(int i=0;i<10;i++){
System.out.println("run-->"+i);
}
}
}
三:线程的生命周期
四:线程的调度和控制
4.1线程优先级
/*
线程优先级高的获取的CPU时间片相对多一些。
优先级:1-10
最低 1
最高 10
默认 5
*/
public class ThreadTest05
{
public static void main(String[] args){
System.out.println(Thread.MAX_PRIORITY); //10
System.out.println(Thread.MIN_PRIORITY); //1
System.out.println(Thread.NORM_PRIORITY); //5
Thread t1 = new Processor3();
t1.setName("t1");
Thread t2 = new Processor3();
t2.setName("t2");
System.out.println(t1.getPriority()); //5
System.out.println(t2.getPriority()); //5
//设置优先级
t1.setPriority(5);
t2.setPriority(6);
//启动线程
t1.start();
t2.start();
}
}
/**
*
*/
class Processor3 extends Thread
{
public void run(){
for(int i=0;i<50;i++){
System.out.println(Thread.currentThread().getName()+"--->" + i);
}
}
}
4.2Thread.sleep
package com.xxx.thread.chapter09;
/*
1.Thread.sleep(毫秒);
2.sleep方法是一个静态方法.
3.该方法的作用:阻塞当前线程.腾出CPU,让给其他线程。
*/
public class ThreadTest06
{
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Processor4();
t1.setName("t1");
t1.start();
//阻塞主线程
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
Thread.sleep(500);
}
}
}
class Processor4 extends Thread
{
//Thread中的run方法不抛出异常,所以重写run方法之后,在run方法的声明位置上不能使用throws
//所以run方法中的异常只能try...catch...
//方法重写:返回值 方法名 方法参数必须一致,子类重写方法的访问权限要比父类的大,
//子类不能抛出比父类重写方法范围更大的异常和不能抛出父类重写方法没有的异常
public void run(){
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
try{
Thread.sleep(1000); //让当前线程阻塞1S。
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
4.3Thread.yield
/*
Thread.yield();
1.该方法是一个静态方法.
2.作用:给同一个优先级的线程让位。但是让位时间不固定。
3.和sleep方法相同,就是yield时间不固定。
*/
public class ThreadTest10
{
public static void main(String[] args){
Thread t = new Processor8();
t.setName("t");
t.start();
//主线程中
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
class Processor8 extends Thread
{
public void run(){
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
if(i%20==0){
Thread.yield();
}
}
}
}
4.4t.join(成员方法)
package com.xxx.thread.chapter09;
/*
线程的合并
*/
public class ThreadTest11
{
public static void main(String[] args) throws Exception{
Thread t = new Thread(new Processor9());
t.setName("t");
t.start();
//合并线程
t.join(); //t和主线程合并. 单线程的程序.
//主线程
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
class Processor9 implements Runnable
{
public void run(){
for(int i=0;i<5;i++){
try{
Thread.sleep(1000);
}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
4.5t.interrupt(中断)
/*
某线程正在休眠,如果打断它的休眠.
以下方式依靠的是异常处理机制。
*/
public class ThreadTest08
{
public static void main(String[] args) throws Exception{
//需求:启动线程,5S之后打断线程的休眠.
Thread t = new Thread(new Processor6());
//起名
t.setName("t");
//启动
t.start();
//5S之后
Thread.sleep(5000);
//打断t的休眠.
t.interrupt();
}
}
class Processor6 implements Runnable
{
public void run(){
try{
Thread.sleep(100000000000L);
System.out.println("HelloWorld!");
}catch(InterruptedException e){
//e.printStackTrace();
}
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
4.6如何正确的停止一个线程
/*
如何正确的更好的终止一个正在执行的线程.
需求:线程启动5S之后终止。
*/
public class ThreadTest09
{
public static void main(String[] args) throws Exception{
Processor7 p = new Processor7();
Thread t = new Thread(p);
t.setName("t");
t.start();
//5S之后终止.
Thread.sleep(5000);
//终止
p.run = false;
}
}
class Processor7 implements Runnable
{
boolean run = true;
public void run(){
for(int i=0;i<10;i++){
//给需要执行的线程的主体代码设置一个开关
if(run){
try{Thread.sleep(1000);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"-->" + i);
}else{
return;
}
}
}
}
五:线程的同步
a.线程模型分为:异步编程模型和同步编程模型
异步编程模型:
线程t1和线程t2,各自执行各自的任务,谁也不用等谁,这种编程模型就叫异步编程模型。
同步编程模型:
线程t1和线程t2,执行的时候,需要排队执行,两个线程之间发生了等待关系,这就是同步编程模型。
异步就是并发,同步就是排队
b.什么条件下数据在多线程并发的环境下会存在安全问题?
三个条件
- 多线程并发
- 有共享数据
- 共享数据有修改的行为
满足以上三个条件就可能会存在安全问题
c.怎么解决线程安全问题:
线程排队执行,不能并发,用排队执行解决线程安全问题,这种机制称为“线程同步机制”
5.1.为什么需要线程同步
多线程带来的安全问题:
假设没有对线程进行处理,依旧是多线程并发:有两个人同时对一张银行卡进行取钱操作,当第一个人取了钱由于网络波动没有及时更新余额,这时另一个人也对该银行卡执行取钱操作,由于第一个人取了钱余额没有更新,这时候钱总数是不变的,当第二个人取了同样多的钱,之后账户上只会显示变了一份的钱,但实际上却取了两份钱,这就会出现数据错误。
5.2.使用线程同步
线程同步,指某一个时刻,指允许一个线程来访问共享资源,线程同步其实是对对象加锁, 如果对象中的方法都是同步方法,那么某一时刻只能执行一个方法,采用线程同步解决以上 的问题,我们只要保证线程一操作 s 时,线程 2 不允许操作即可,只有线程一使用完成 s 后, 再让线程二来使用 s 变量。
六:synchronized关键字的使用
synchorized有三种写法:
一:同步代码块:灵活
synchorized(线程共享对象){}
二:在实例方法上使用synchorize
表示共享对象一定是this
并且同步代码块是整个方法体
三:在静态方法上使用synchorized
表示类锁
类锁永远只有一把
就算创建100个对对象,类锁也只有一把
6.1同步代码块
package com.xxx.thread.chapter09;
/*
以下程序使用线程同步机制保证数据的安全。
*/
public class ThreadTest13
{
public static void main(String[] args){
//创建一个公共的账户
Account act = new Account("actno-001",5000.0);
//创建线程对同一个账户取款
Thread t1 = new Thread(new Processor11(act));
Thread t2 = new Thread(new Processor11(act));
t1.start();
t2.start();
}
}
//取款线程
class Processor11 implements Runnable
{
//账户
Account act;
//Constructor
Processor11(Account act){
this.act = act;
}
public void run(){
act.withdraw(1000.0);
System.out.println("取款1000.0成功,余额:" + act.getBalance());
}
}
//账户
class Account2
{
private String actno;
private double balance;
public Account2(){}
public Account2(String actno,double balance){
this.actno = actno;
this.balance = balance;
}
//setter and getter
public void setActno(String actno){
this.actno = actno;
}
public void setBalance(double balance){
this.balance = balance;
}
public String getActno(){
return actno;
}
public double getBalance(){
return balance;
}
//对外提供一个取款的方法
public void withdraw(double money){ //对当前账户进行取款操作
//把需要同步的代码,放到同步语句块中.
/*
原理:t1线程和t2线程.
t1线程执行到此处,遇到了synchronized关键字,就会去找this的对象锁,
如果找到this对象锁,则进入同步语句块中执行程序。当同步语句块中的代码
执行结束之后,t1线程归还this的对象锁。
在t1线程执行同步语句块的过程中,如果t2线程也过来执行以下代码,也遇到
synchronized关键字,所以也去找this的对象锁,但是该对象锁被t1线程持有,
只能在这等待this对象的归还。
synchronized后面的这个小括号中传递的数据非常重要,
这个数据是多线程共享的数据,只有共享才能达多线程排队
java语言都要一把锁,其实这把锁就是一个标记
100个对象100把锁
*/
synchronized(this){
double after = balance - money;
//延迟
try{Thread.sleep(1000);}catch(Exception e){}
//更新
this.setBalance(after);
}
}
}
6.2在实例方法上使用synchorized和在静态方法上使用synchronized
package com.xxx.thread.chapter09;
/*
以下程序使用线程同步机制保证数据的安全。
*/
public class ThreadTest14
{
public static void main(String[] args){
//创建一个公共的账户
Account3 act = new Account3("actno-001",5000.0);
//创建线程对同一个账户取款
Thread t1 = new Thread(new Processor12(act));
Thread t2 = new Thread(new Processor12(act));
t1.start();
t2.start();
}
}
//取款线程
class Processor12 implements Runnable
{
//账户
Account3 act;
//Constructor
Processor12(Account3 act){
this.act = act;
}
public void run(){
act.withdraw(1000.0);
System.out.println("取款1000.0成功,余额:" + act.getBalance());
}
}
//账户
class Account3
{
private String actno;
private double balance;
public Account3(){}
public Account3(String actno,double balance){
this.actno = actno;
this.balance = balance;
}
//setter and getter
public void setActno(String actno){
this.actno = actno;
}
public void setBalance(double balance){
this.balance = balance;
}
public String getActno(){
return actno;
}
public double getBalance(){
return balance;
}
//对外提供一个取款的方法
//synchronized关键字添加到成员方法上,线程拿走的默认是this的对象锁。
//synchorized(this){}
//synchorized出现在方法上,表示整个方法需要同步,可能会无故扩大程序同步的范围
public synchronized void withdraw(double money){ //对当前账户进行取款操作
//把需要同步的代码,放到同步语句块中.
/*
原理:t1线程和t2线程.
t1线程执行到此处,遇到了synchronized关键字,就会去找this的对象锁,
如果找到this对象锁,则进入同步语句块中执行程序。当同步语句块中的代码
执行结束之后,t1线程归还this的对象锁。
在t1线程执行同步语句块的过程中,如果t2线程也过来执行以下代码,也遇到
synchronized关键字,所以也去找this的对象锁,但是该对象锁被t1线程持有,
只能在这等待this对象的归还。
*/
double after = balance - money;
//延迟
try{Thread.sleep(1000);}catch(Exception e){}
//更新
this.setBalance(after);
}
}
6.3面试题:深入synchronized
package com.xxx.thread.chapter09;
/*
面试题
*/
public class ThreadTest15
{
public static void main(String[] args) throws Exception{
MyClass mc = new MyClass();
Processor13 p = new Processor13(mc);
Thread t1 = new Thread(p);
t1.setName("t1");
Thread t2 = new Thread(p);
t2.setName("t2");
//启动线程
t1.start();
//延迟(保证t1线程先启动,并执行run)
Thread.sleep(1000);
t2.start();
}
}
class Processor13 implements Runnable
{
MyClass mc;
Processor13(MyClass mc){
this.mc = mc;
}
public void run(){
if(Thread.currentThread().getName().equals("t1")){
mc.m1();
}
if(Thread.currentThread().getName().equals("t2")){
mc.m2();
}
}
}
class MyClass
{
public synchronized void m1(){
//休眠
try{
Thread.sleep(10000);
}catch(Exception e){}
System.out.println("m1....");
}
/*
//m2方法的执行不需要等m1的结束,因为m2方法上没有synchronized
public void m2(){
System.out.println("m2....");
}
*/
//m2方法会等m1方法结束,t1,t2共享同一个mc,并且m1和m2方法上都有synchronized
public synchronized void m2(){
System.out.println("m2....");
}
}
2.是否共享一个相同的数据对象
当synchronized(共享数据对象){},其中的“共享数据对象”不相同是不用考虑锁得问题,因为不是相同的数据对象。
package com.xxx.thread.chapter09;
/*
面试题:
synchronized(共享数据对象){}
*/
public class ThreadTest16
{
public static void main(String[] args) throws Exception{
MyClass1 mc1 = new MyClass1();
MyClass1 mc2 = new MyClass1();
Processor14 p1 = new Processor14(mc1);
Processor14 p2 = new Processor14(mc2);
Thread t1 = new Thread(p1);
t1.setName("t1");
Thread t2 = new Thread(p2);
t2.setName("t2");
//启动线程
t1.start();
//延迟(保证t1线程先启动,并执行run)
Thread.sleep(1000);
t2.start();
}
}
class Processor14 implements Runnable
{
MyClass1 mc;
Processor14(MyClass1 mc){
this.mc = mc;
}
public void run(){
if(Thread.currentThread().getName().equals("t1")){
mc.m1();
}
if(Thread.currentThread().getName().equals("t2")){
mc.m2();
}
}
}
class MyClass1
{
public synchronized void m1(){
//休眠
try{
Thread.sleep(10000);
}catch(Exception e){}
System.out.println("m1....");
}
//m2方法不会等m1方法结束,t1,t2不共享同一个mc(对象)
public synchronized void m2(){
System.out.println("m2....");
}
}
3.区分synchronized在静态方法上和实例方法上的区别
对象锁可以有很多个,但是类锁之后一个,如果synchronized关键字是添加在静态方法上,则说名这是一个类锁,因为静态方法需要类来执行。
当一个类被锁住,说明后面创建的任何实例对象对不能再用该类了,除非类锁的相关代码执行结束,类锁被放开。
package com.xxx.thread.chapter09;
/*
面试题:
类锁,类只有一个,所以锁是类级别的,只有一个.
*/
public class ThreadTest17
{
public static void main(String[] args) throws Exception{
Thread t1 = new Thread(new Processor17());
Thread t2 = new Thread(new Processor17());
t1.setName("t1");
t2.setName("t2");
t1.start();
//延迟,保证t1先执行
Thread.sleep(1000);
t2.start();
}
}
class Processor17 implements Runnable
{
public void run(){
if("t1".equals(Thread.currentThread().getName())){
MyClass17.m1();
}
if("t2".equals(Thread.currentThread().getName())){
MyClass17.m2();
}
}
}
class MyClass17
{
//synchronized添加到静态方法上,线程执行此方法的时候会找类锁。
public synchronized static void m1(){
try{Thread.sleep(10000);}catch(Exception e){}
System.out.println("m1....");
}
//不会等m1结束,因为该方法没有被synchronized修饰
/*
public static void m2(){
System.out.println("m2...");
}
*/
//m2方法等m1结束之后才能执行,该方法有synchronized
//线程执行该代码需要“类锁”,而类锁只有一个。
public synchronized static void m2(){
System.out.println("m2...");
}
}