继承Thread类创建线程类
public class FirstThread extends Thread
{
private int i=0;
public void run(){
for(;i<100;i++){
System.out.println(getName()+"#"+i);
}
}
public static void main(String[] args){
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName());
if(i==20){
new FirstThread().start(); //创建线程,创建的是一个单独的实例不共享类的变量
new FirstThread().start(); //创建线程
}
}
}
}
通过Runable接口创建线程类
/**
* Description:
* <br/>网站: <a href="http://www.crazyit.org">疯狂Java联盟</a>
* <br/>Copyright (C), 2001-2016, Yeeku.H.Lee
* <br/>This program is protected by copyright laws.
* <br/>Program Name:
* <br/>Date:
* @author Yeeku.H.Lee kongyeeku@163.com
* @version 1.0
*/
// 通过实现Runnable接口来创建线程类
public class SecondThread implements Runnable
{
private int i ;
// run方法同样是线程执行体
public void run() //同样是线程的执行体
{
for ( ; i < 100 ; i++ )
{
// 当线程类实现Runnable接口时,
// 如果想获取当前线程,只能用Thread.currentThread()方法。
System.out.println(Thread.currentThread().getName()
+ " " + i);
}
}
public static void main(String[] args)
{
for (int i = 0; i < 100; i++)
{
System.out.println(Thread.currentThread().getName()
+ " " + i);
if (i == 20)
{
SecondThread st = new SecondThread(); // ① 这里创建实现了Runable接口的类的实例
// 通过new Thread(target , name)方法创建新线程
new Thread(st , "新线程1").start(); //这里的意思是创建一个线程的target,也就是线程的目标
new Thread(st , "新线程2").start();
}
}
}
}
通过使用Callable和Future创建线程
import java.util.concurrent.*;
public class ThirdThread implements Callable<Integer>{
public Integer call(){
int i=0;
for(;i<100;i++){
System.out.println(Thread.currentThread().getName()+"循环变量i的值"+i);
}
return i;
}
public static void main(String[] args){
ThirdThread rt=new ThirdThread();
FutureTask<Integer> task=new FutureTask<Integer>(rt);
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+
" 循环变量i的值:"+i);
if(i==20){
new Thread(task,"有返回值的线程").start();
}
}
try{
System.out.println("子线程的返回值:"+task.get()); //这里居然有子线程的返回值,通过task来包装Callable类的rt实例,然后通过get方法来获取call()的返回值
}
catch(Exception ex){
ex.printStackTrace();
}
}
}
线程
线程的生命周期
在线程的生命周期中,它要经过新建,就绪,运行,阻塞,死亡5种状态。
当创建一个新的线程
,也和其他的java对象一样,只是由java虚拟机为其分配内存,并且初始化其成员的变量。此时的线程对象并没有表现出任何的线程的动态特征,程序也不会执行线程的线程执行体。
当调用了线程的start()方法之后,
线程就处于就绪状态,java虚拟机为为其创建方法调用栈和程序计数器,处于这个状态的线程并没有开始运行,只是表示该线程可以运行。至于该线程何时开始运行,取决于JVM里线程调度器的调度;
不要去尝试调用线程对象中run()方法:
因为这样run()方法会立即执行,并且在返回之前无法并发执行,并且调用了之后线程就不再处于新建状态,不能再次执行start(),会引发异常
线程调度策略:抢占式调度策略
线程进入阻塞状态
sleep()
调用一个阻塞式IO方法,在该方法返回之前,该线程被阻塞
线程视图获取一个同步监视器,但是该同步监视器,但是该同步监视器正在被其他线程所持有。
线程正在等待某个通知。
程序调用了现成的suspend()方法将线程挂起。但是这个方法容易导致死锁,所以应该尽量避免使用这个方法。
控制线程
join线程:如果想让当前执行的等待另一个线程完成,用join方法
后台线程:setDaemon()
线程睡眠:sleep()
线程让步: field()
设置线程优先级:setPriority()
线程同步安全问题:
举例:银行取钱问题,有一个账户,但是创建两个线程对账户进行取钱操作
账户类
public class Account
{
private String accountNo;
private double balance;
public Account(){}
public Account(String accountNo,double balance){
this.accountNo=accountNo;
this.balance=balance;
}
public void setAccount(String accountNo){
this.accountNo=accountNo;
}
public String getAccount(){
return this.accountNo;
}
public void setBalance(double balance){
this.balance=balance;
}
public double getBalance(){
return this.balance;
}
public int hashCode(){
return accountNo.hashCode();
}
public boolean equals(Object obj){
if(obj==this){
return true;
}
if(obj!=null&&obj.getClass()==this.getClass()){
Account at=(Account)obj;
return at.getAccount().equals(this.accountNo);
}
return false;
}
}
取钱线程:
public class DrawThread extends Thread
{
private Account account;
private double drawAccount;
public DrawThread(String name,double drawAccount,Account account){
super(name);
this.drawAccount=drawAccount;
this.account=account;
}
//线程的运行体,取出要取的钱
public void run(){
//使用account来作为同步监视器,任何线程进入下面的代码块之前
//必须先获得对account的锁定,-其他线程无法获得锁,也就无法修改它
synchronized(account){
if(drawAccount<=account.getBalance()){
System.out.println("取钱成功,吐出钞票:"+drawAccount+"元");
try{
Thread.sleep(1);
}
catch(InterruptedException ex){
ex.printStackTrace();
}
account.setBalance(account.getBalance()-drawAccount);
System.out.println("\t余额为:"+account.getBalance());
}
else{
System.out.println("取钱失败");
}
}
//同步代码块消失,该线程释放同步锁
}
}
取钱动作:
public class Draw
{
public static void main(String[] args){
Account at=new Account(“陈阔”,1000);
new DrawThread(“第一个取钱线程”,800,at).start();
new DrawThread(“第二个取钱线程”,800,at).start();
}
}
使用synchronized同步监视器来解决
synchronized(obj){
同步监视代码块
}
使用同步方法
public class Account2
{
private String accountNo;
private double balance;
public Account2(){}
public Account2(String accountNo,double balance){
this.accountNo=accountNo;
this.balance=balance;
}
public void setAccount(String accountNo){
this.accountNo=accountNo;
}
public String getAccount(){
return this.accountNo;
}
public double getBalance(){
return this.balance;
}
//使用同步修饰符对方法进行修饰,同步方法的同步监视器是this,也就是调用这个方法的对象
//因此对于同一个Account账户而言,任意时刻只能有一个线程获得对Account的对象的锁定,然后进入draw()方法执行取钱操作
public synchronized void draw(double drawAmount){
if(drawAmount<=balance){
System.out.println("取钱成功"+drawAmount+"元");
//问题:为什么要在这里进行异常判断
try{
Thread.sleep(1);
}
catch(InterruptedException ex){
ex.printStackTrace();
}
this.balance=this.getBalance()-drawAmount;
System.out.println("\t余额为:"+this.getBalance());
}
else{
System.out.println("取钱失败");
}
}
public int hashCode(){
return accountNo.hashCode();
}
public boolean equals(Object obj){
if(obj==this){
return true;
}
if(obj!=null&&obj.getClass()==this.getClass()){
Account at=(Account)obj;
return at.getAccount().equals(this.accountNo);
}
return false;
}
}
线程的运行体代码
public class DrawThread2 extends Thread
{
private Account2 account;
private double drawAccount;
public DrawThread2(String name,double drawAccount,Account2 account){
super(name);
this.drawAccount=drawAccount;
this.account=account;
}
//线程的运行体,取出要取的钱
public void run(){
//这个方法在account自己的类中是使用了synchronized修饰符的,运行时就会对运行和这个方法的对象进行锁定,也就是account
account.draw(drawAccount);
}
}
提示:在account里定义的draw()方法,不在run()中实现取钱逻辑,这种做法更加符合面向对象的规则,在面向对象中有一种就行的设计方法
Domain Driven Design(领域驱动设计):
认为每个类都应该是完备的领域对象,刚刚设计的类属于线程安全的类(该类的对象可以被多个线程安全地访问,每个线程调用该对象的任意方法都将得到正确的结果)
注意:可变类的线程安全是以降低程序的运行效率来作为代价的,为了减少线程安全所带来的负面影响,采用如下策略:
不要对线程安全的类的所有方法都进行同步,只对那些会改变竞争资源的方法进行同步。
如果可变类有两种运行环境:单线程环境和多线程环境,应该为该可变类提供两种版本。在单线程中保证性能,多线程安全中使用线程安全的版本
线程通信:
使用Object类提供的wait(), notify(), notifyAll() 三个方法
wait():导致当前线程等待,知道其他线程调用该同步监视器的notify()方法或notifyAll()方法来唤醒该线程.调用wait()方法的当前线程会释放对该同步监视器的锁定。
该方法有三种重载方式:无参(一直等待,知道其他线程通知);带毫秒参数的wait; 带毫秒微毫秒参数的wait();.
notify():唤醒此同步监视器上等待的某个单线程。
银行多个一个取款者和多个存款者
import java.util.concurrent.locks.*;
public class Account4
{
private String accountNo;
private double balance;
//设置标志位判断是否有存款
private boolean flag=false;
public Account4(){}
public Account4(String accountNo,double balance){
this.accountNo=accountNo;
this.balance=balance;
}
public void setAccount(String accountNo){
this.accountNo=accountNo;
}
public String getAccount(){
return this.accountNo;
}
public double getBalance(){
return this.balance;
}
//使用同步修饰符对方法进行修饰,同步方法的同步监视器是this,也就是调用这个方法的对象
//因此对于同一个Account账户而言,任意时刻只能有一个线程获得对Account的对象的锁定,然后进入draw()方法执行取钱操作
//存钱函数
public synchronized void draw(double drawAmount){
try{
if(!flag){
wait();
}
else{
System.out.println(Thread.currentThread().getName()+"取钱成功"+drawAmount+"元");
this.balance=this.getBalance()-drawAmount;
System.out.println("\t余额为:"+balance);
flag=false;
notifyAll();
}
}
catch(InterruptedException ex){
ex.printStackTrace();
}
}
//取钱函数
public synchronized void deposit(double depositAmount){
try{
if(!flag){
System.out.println(Thread.currentThread().getName()+"存钱成功"+depositAmount);
balance+=depositAmount;
System.out.println("\t余额为"+balance+"元");
flag=true;
notifyAll();
}
else{
wait();
}
}
catch(InterruptedException ex){
ex.printStackTrace();
}
}
public int hashCode(){
return accountNo.hashCode();
}
public boolean equals(Object obj){
if(obj==this){
return true;
}
if(obj!=null&&obj.getClass()==this.getClass()){
Account at=(Account)obj;
return at.getAccount().equals(this.accountNo);
}
return false;
}
}
public class DepositThread extends Thread{
private Account4 at;
private double depositAmount;
public DepositThread(String name,double depositAmount,Account4 at){
super(name);
this.at=at;
this.depositAmount=depositAmount;
}
public void run(){
for (int i=0;i<100 ;i++ )
{
at.deposit(depositAmount);
}
}
}
public class DrawThread2 extends Thread
{
private Account4 account;
private double drawAccount;
public DrawThread2(String name,double drawAccount,Account4 account){
super(name);
this.drawAccount=drawAccount;
this.account=account;
}
//线程的运行体,取出要取的钱
public void run(){
//这个方法在account自己的类中是使用了synchronized修饰符的,运行时就会对运行和这个方法的对象进行锁定,也就是account
for (int i=0;i<100;i++ )
{
account.draw(drawAccount);
}
}
}
public class Draw
{
public static void main(String[] args){
/*Account at=new Account("陈阔",1000);
new DrawThread("第一个取钱线程",800,at).start();
new DrawThread("第二个取钱线程",800,at).start();
*/
/*
Account2 at2=new Account2("小明",1000);
new DrawThread2("取钱线程第二种第一个",800,at2).start();
new DrawThread2("取钱线程第二种第2个",800,at2).start();
*/
/*Account3 at3=new Account3("小红",1000);
new DrawThread2("使用同步锁的取钱线程",800,at3).start();
new DrawThread2("使用同步锁的取钱线程",800,at3).start();
*/
Account4 at4=new Account4("小刚",1000);
new DrawThread2("取钱者",1000,at4).start();
new DepositThread("存钱者甲",1000,at4).start();
new DepositThread("存钱者乙",1000,at4).start();
new DepositThread("存钱者丙",1000,at4).start()
}
}
线程阻塞和死锁:如图所示的阻塞并不是死锁,对于这种情况,取钱者线程已经执行结束,而存款者线程只是等待其他取款者线程来取钱而已,而不是等待其他线程释放同步监视器。