# 1线程的创建和启动
1.1thread类创建线程类
package ThreadLearnDemo01;
public class FirstThread extends Thread{
private int i;
public void run(){
for(;i<100;i++)
{
System.out.println(this.getName()+" "+i);
}
}
public static void main(String[] args) {
for(int i=0;i<100;i++) {
if (i == 30) {
new FirstThread().start();
new FirstThread().start();
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
1.2runnable接口创建线程类
package ThreadLearnDemo01;
public class FirstThread extends Thread{
private int i;
public void run(){
for(;i<100;i++)
{
System.out.println(this.getName()+" "+i);
}
}
public static void main(String[] args) {
for(int i=0;i<100;i++) {
if (i == 30) {
new FirstThread().start();
new FirstThread().start();
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
在运行的时候出现了一种奇怪的现象。 从整体上看,两个线程都是用同一个target firstthread来初始构造的,共享这一个target的资源,但是在运行过程中出现了乱序,这是一种不安全的线程。
1.3使用Callable和Future创建线程
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadLearnDemo02 {
public static void main(String[] args) {
FutureTask<Integer> task=new FutureTask<Integer>((Callable <Integer>)()->{
int j=0;
for(;j<100;j++)
{
System.out.println(Thread.currentThread().getName()+" "+j);
}
return j;
});
int i=0;
for(;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==3)
{
new Thread(task, "Callable创建的线程").start();
}
}
try{
System.out.println("子线程返回值"+task.get());
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
2线程的生命周期
2.1新建和就绪状态
new出来的线程是新建状态,该状态和其他的java对象一样,没有表现出任何线程的动态特征。 调用start()方法之后,java虚拟机会为其创建方法调用栈和程序计数器,线程何时开始运行,去决议jvm里线程调度器的调度。
package ThreadLearnDemo03;
public class ThreadLearnDemo03 extends Thread{
private int i;
public void run(){
for(;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i+this.getName());
}
}
public static void main(String[] args) {
for(int i=0;i<100;i++)
{
System.out.println(Thread.currentThread().getName());
if(i==50){
new ThreadLearnDemo03().run();
new ThreadLearnDemo03().run();
}
}
}
}
2.2运行和阻塞状态
什么时候会进入阻塞状态:
- 线程调用sleep()方法主动放弃所占用的处理器资源。
- 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞
- 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有的。
- 线程在等待某个通知
- 程序调用了现成的suspend方法将该线程挂起,但这个方法容易导致死锁,所以应该尽量避免使用
针对上面情况。发生如下特定情况可以解除上面的阻塞。
- 调用sleep方法的线程经过了指定时间
- 线程调用的阻塞式IO方法已经返回
- 线程成功的获得了试图取得的同步监视器
- 线程正在等待某个通知,其他线程发出了一个通知。
- 处于挂起状态的县城被调用了resume()恢复方法。
2.3线程死亡
线程结束方式:
- run()或call()方法执行完成,线程正常结束
- 线程抛出一个未捕获的Exception或Error
- 直接调用该线程的stop方法结束该线程——该方法容易导致死锁,不推荐使用
当主线程结束时,其他线程不受影响,并不会随之结束,一旦子线程启动起来后,它就拥有和主线程相同的地位,不受主线程的影响 测试线程是否死亡的方法:isAlive() 当处与新建和死亡两种状态时,该方法返回false。 不要对一个已经死亡的线程调用start或者run,死亡就是死亡,如果调用会引发IllegalThreadState Exception异常
3.控制线程
3.1join线程
package ThreadLearnDemo04;
public class ThreadLearnDemo04 extends Thread{
public ThreadLearnDemo04(String name)
{
super(name);
}
public void run(){
for(int i=0;i<100;i++)
{
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args) throws InterruptedException {
new ThreadLearnDemo04("新线程").start();
for(int i=0;i<100;i++)
{
if(i==20)
{
ThreadLearnDemo04 threadLearnDemo04 = new ThreadLearnDemo04("被join的线程");
threadLearnDemo04.start();
threadLearnDemo04.join();
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
主线程经过join处于等待状态,新线程和被join线程并发执行。 主线程必须等待被join线程执行完之后才能继续执行。
3.2后台线程
后台线程的任务是为其它线程提供服务,如果前台线程都死亡,后台会自动死亡。使用setDaemon(true)将指定线程设置成后台线程。
package ThreadLearnDemo05;
public class ThreadLearnDemo05 extends Thread{
public void run()
{
for(int i=0;i<1000;i++)
{
System.out.println(getName()+" "+i+" "+this.getName());
}
}
public static void main(String[] args) {
ThreadLearnDemo05 threadLearnDemo05 = new ThreadLearnDemo05();
threadLearnDemo05.setDaemon(true);
threadLearnDemo05.start();
for(int i=0;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
在这个程序中,创建的子线程不会跑完
3.3线程睡眠sleep
staic void sleep(long millis) static void sleep(long millis,int nanos)
package ThreadLearnDemo06;
import java.util.Date;
public class ThreadLearnDemo06 {
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<10;i++){
System.out.println("当前时间:"+new Date());
Thread.sleep(1000);
}
}
}
这个程序会每隔一秒输出一条语句
3.4线程让步:yield
不建议使用,通过分配给线程优先级,然后yield让系统线程调度器使所有线程处于就绪态,重新调度一次,在多CUP机器效果不明显。
3.5改变线程优先级
package com.luojias.threadlearn.demo01;
public class Demo01 extends Thread{
public Demo01(String name)
{
super(name);
}
public void run()
{
for(int i=0;i<1000;i++)
{
System.out.println(getName()+" "+getPriority()+" "+" "+i);
}
}
public static void main(String[] args) {
Thread.currentThread().setPriority(6);
for(int i=0;i<30;i++)
{
if(i==10)
{
Demo01 low = new Demo01("低级");
low.start();
System.out.println("低级创建时候的优先级"+low.getPriority());
low.setPriority(MIN_PRIORITY);
}
if(i==20)
{
Demo01 high=new Demo01("高级");
System.out.println("高级创建时候的优先级"+high.getPriority());
high.setPriority(MAX_PRIORITY);
high.start();
}
}
}
}
输出图: 开始: 最后: 得出如下结论:线程的默认优先级是主线程的优先级 优先度高的线程获得更多的执行机会。 另外需要注意: 由于操作系统不同,应该使用MAX_PRIORITY、MIN_PRIORITY、NORM_PRIORITY三个静态常量来设置优先级
4.线程同步
4.1线程安全问题
银行取钱问题
package com.luojias.threadlearn.demo02;
public class Account {
private String accountNo;
private double balance;
public Account(){}
public Account (String accountNo,double balance)
{
this.accountNo=accountNo;
this.balance=balance;
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public int hasCode()
{
return accountNo.hashCode();
}
public boolean equals(Object obj)
{
if(this==obj) return true;
if(obj!=null&&obj.getClass()==Account.class)
{
Account target=(Account)obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
}
package com.luojias.threadlearn.demo02;
public class DrawThread extends Thread{
private Account account;
private double drawAmount;
public DrawThread(String name,Account account,double drawAmount)
{
super(name);this.account=account;this.drawAmount=drawAmount;
}
public void run() {
if (account.getBalance() >= drawAmount) {
System.out.println(getName() + "取钱成功吐出钞票:" + drawAmount);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.setBalance(account.getBalance() - drawAmount);
System.out.println("余额为:" + account.getBalance());
} else {
System.out.println("余额不足");
}
}
}
package com.luojias.threadlearn.demo02;
public class Demo02 {
public static void main(String[] args) {
Account acct=new Account("123456",1000);
new DrawThread("甲",acct,900).start();
new DrawThread("乙",acct,800).start();
}
}
运行main函数后会出现各种错误,有的时候余额都是负数,有的时候都是整数,有的时候一正一负。
4.2同步代码块
为了解决上面出现的问题可以使用synchronized,也就是加锁操作
package com.luojias.threadlearn.demo02;
public class DrawThread extends Thread{
private Account account;
private double drawAmount;
public DrawThread(String name,Account account,double drawAmount)
{
super(name);this.account=account;this.drawAmount=drawAmount;
}
public void run() {
synchronized (account) {
if (account.getBalance() >= drawAmount) {
System.out.println(getName() + "取钱成功吐出钞票:" + drawAmount);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.setBalance(account.getBalance() - drawAmount);
System.out.println("余额为:" + account.getBalance());
} else {
System.out.println("余额不足");
}
}
}
}
这样有一个线程在修改的时候,account就会被锁住,别的线程,无法使用account,在修改完毕后,释放锁
4.3同步方法
同步方法能够方便地实现线程安全的类,具有以下特征
- 该类的对象可以被多个线程安全的访问
- 每个线程调用该对象的任一方法之后都将得到正确结果
- 每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态
这里直接锁run是不能锁住的,因为每次run的是一个新的线程。 所以必须在共享的资源里加锁 把锁加在共享的Account解决问题
package com.luojias.threadlearn.demo02;
public class Account {
private String accountNo;
private double balance;
public Account(){}
public Account (String accountNo,double balance)
{
this.accountNo=accountNo;
this.balance=balance;
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public int hasCode()
{
return accountNo.hashCode();
}
public boolean equals(Object obj)
{
if(this==obj) return true;
if(obj!=null&&obj.getClass()==Account.class)
{
Account target=(Account)obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
public synchronized void draw(double drawAmount) {
if (balance >= drawAmount) {
System.out.println( accountNo+ "取钱成功吐出钞票:" + drawAmount);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
setBalance(balance - drawAmount);
System.out.println("余额为:" + balance);
} else {
System.out.println("余额不足");
}
}
}
4.4释放同步监视器的锁定
结束:
- 方法或者代码块结束
- 遇到return break终止了代码块方法
- 出现了未处理的异常或者错误
- 执行了wait方法,线程暂停
不会释放同步监视器的情况:
- 调用sleep(),yield()暂停当前线程的执行。
- 其他线程调用了该线程的suspend()方法将线程挂起,避免使用suspend()和resume()方法控制线程
4.5 同步锁(lock)
用lock可以设置时间。如果锁住的时间过长,然后释放
package com.luojias.threadlearn.demo02;
import java.util.concurrent.locks.ReentrantLock;
public class Account {
private String accountNo;
private final ReentrantLock lock=new ReentrantLock();
private double balance;
public Account(){}
public Account (String accountNo,double balance)
{
this.accountNo=accountNo;
this.balance=balance;
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public int hasCode()
{
return accountNo.hashCode();
}
public boolean equals(Object obj)
{
if(this==obj) return true;
if(obj!=null&&obj.getClass()==Account.class)
{
Account target=(Account)obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
public synchronized void draw(double drawAmount) {
lock.lock();
try {
if (balance >= drawAmount) {
System.out.println(accountNo + "取钱成功吐出钞票:" + drawAmount);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
setBalance(balance - drawAmount);
System.out.println("余额为:" + balance);
} else {
System.out.println("余额不足");
}
}
finally {
lock.unlock();
}
}
}
4.6死锁
这是代码块都在等待对方释放资源,然后都无法继续进行的时候,称为死锁。
5线程通信
5.1传统的线程通信和condition控制的通信
两者用法基本相同,一个是调用wait notifyall notify另一个是调用await signal signalall 贴上测试代码,可能有点繁琐,其实可以把123三种方法写成同一个方法。
package com.luojias.threadlearn.demo03;
import java.util.Objects;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Account {
private String accountNo;
private double balance;
private boolean flag=false;
private final ReentrantLock lock= new ReentrantLock();
private final Condition cond= lock.newCondition();
public Account(){ }
public Account(String accountNo,double balance){this.accountNo=accountNo;this.balance=balance;}
public String getAccountNo() {
return accountNo;
}
public double getBalance() {
return balance;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Account account = (Account) o;
return accountNo.equals(account.accountNo);
}
public int hashCode() {
return Objects.hash(accountNo);
}
public void draw(double balance) {
lock.lock();
try {
if (!flag) {
cond.await();
} else {
System.out.println("取钱成功" + balance + " " + Thread.currentThread().getName());
flag = false;
balance = 0;
cond.signalAll();
}
}catch(InterruptedException er){
er.printStackTrace();
}
finally {
lock.unlock();
}
}
public void deposit(double money) {
lock.lock();
try {
if(flag){
cond.await();
}
else{
balance=money;
flag=true;
System.out.println("存钱成功1");
cond.signalAll();
}
}catch (InterruptedException ex) {
ex.printStackTrace();
}finally {
lock.unlock();
}
}
public void deposit2(double money) {
lock.lock();
try {
if(flag){
cond.await();
}
else{
balance=money;
flag=true;
System.out.println("存钱成功2");
cond.signalAll();
}
}catch (InterruptedException ex) {
ex.printStackTrace();
}finally {
lock.unlock();
}
}
public void deposit3(double money) {
lock.lock();
try {
if(flag){
cond.await();
}
else{
balance=money;
flag=true;
System.out.println("存钱成功3");
cond.signal();
}
}catch (InterruptedException ex) {
ex.printStackTrace();
}finally {
lock.unlock();
}
}
}
package com.luojias.threadlearn.demo03;
public class DepositThread extends Thread{
private Account account;
private double depositAmount;
public DepositThread(String name,Account account,double depositAmount){
super(name);
this.account=account;
this.depositAmount=depositAmount;
}
@Override
public void run() {
for(int i=0;i<100;i++)
{
account.deposit(depositAmount);
}
}
}
package com.luojias.threadlearn.demo03;
public class DepositThread2 extends Thread{
private Account account;
private double depositAmount;
public DepositThread2(String name,Account account,double depositAmount){
super(name);
this.account=account;
this.depositAmount=depositAmount;
}
@Override
public void run() {
for(int i=0;i<100;i++)
{
account.deposit(depositAmount);
}
}
}
package com.luojias.threadlearn.demo03;
public class DepositThread3 extends Thread{
private Account account;
private double depositAmount;
public DepositThread3(String name,Account account,double depositAmount){
super(name);
this.account=account;
this.depositAmount=depositAmount;
}
@Override
public void run() {
for(int i=0;i<100;i++)
{
account.deposit(depositAmount);
}
}
}
package com.luojias.threadlearn.demo03;
public class DrawThread extends Thread{
private Account account;
private double drawAmount;
public DrawThread(String name,Account account,double drawAmount){
super(name);
this.drawAmount=drawAmount;
this.account=account;
}
@Override
public void run() {
for(int i=0;i<100;i++)
{
account.draw(drawAmount);
}
}
}
package com.luojias.threadlearn.demo03;
public class Test {
public static void main(String[] args) {
Account acct=new Account("1234567",0);
new DrawThread("取钱者",acct,800).start();
new DepositThread("存期者1",acct,800).start();
new DepositThread2("存期者2",acct,800).start();
new DepositThread3("存期者3",acct,800).start();
}
}
5.2BlockingQuene控制线程通信
小demo
package com.luojias.threadlearn.demo04;
import java.util.concurrent.ArrayBlockingQueue;
public class BlockingQueueTest {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<String> bq=new ArrayBlockingQueue<String>(2);
bq.put("Java");
bq.put("Java");
bq.put("Java");
}
}
实例:
package com.luojias.threadlearn.demo05;
import java.util.concurrent.BlockingQueue;
public class Consumer extends Thread{
private BlockingQueue<String>bq;
public Consumer(BlockingQueue<String>bq){
this.bq=bq;
}
public void run(){
while (true){
System.out.println(getName()+" 消费者准备消费集合");
try {
Thread.sleep(200);
bq.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"消费完成"+bq);
}
}
}
package com.luojias.threadlearn.demo05;
import java.util.concurrent.BlockingQueue;
public class Producer extends Thread{
private BlockingQueue<String>bq;
public Producer(BlockingQueue<String>bq){
this.bq=bq;
}
public void run(){
String []strArr=new String[]{
"java","i","love"
};
for(int i=0;i<9999999;i++){
System.out.println(getName()+"生产者准备生产集合元素");
try {
Thread.sleep(200);
bq.put(strArr[i%3]);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+" 生产完成 "+bq);
}
}
}
package com.luojias.threadlearn.demo05;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueTest2 {
public static void main(String[] args) {
BlockingQueue<String>bq=new ArrayBlockingQueue<String>(1);
new Producer(bq).start();
new Producer(bq).start();
new Producer(bq).start();
new Consumer(bq).start();
}
}
5.3线程池
未完待续
|