关于java线程的一点总结,有直接从网上搬过来的,有自己的一点看书总结
1.什么是多线程
多线程就是一个程序同时执行多个任务,每一个任务称为一个线程,多线程之间共享数据,线程之间的通信比进程之间的通信方便且消耗少,实际应用中,多线程应用很广泛,如:浏览器同时下载多个图片,同时处理并发请求
2.多线程有什么用
1)我们应用多线程,最主要的就是它可以让一个程序同时执行多个任务的能力
3.线程的说明
3.1线程的举例
调用这个方法就会创建一个单独线程的球
/**
* Adds a bouncing ball to the canvas and starts a thread to make it bounce
*/
public void addBall()
{
Ball ball = new Ball();
comp.add(ball);
Thread t = new Thread(() -> {
try
{
for (int i = 1; i <= STEPS; i++)
{
ball.move(comp.getBounds());
comp.repaint();
Thread.sleep(DELAY);
}
}
catch (InterruptedException e)
{
}
});
t.start();
}
3.2线程的中断
1)当线程执行至run方法体中最后一条语句后,或者出现了在方法中没有捕获的异常时,线程将中止。
2)用interrupt来请求中止线程(以前有个stop方法,如今弃用了,有很多危险,比如锁不能归还等)
对线程调用interrupt,是「通知线程应该中断了」,具体到底中断还是继续运行,应该由被通知的线程自己处理。
使用场景:
package unit14.testInterrupt;
public class Interrupt {
public static void main(String[] args) {
runTest();
}
public static void runTest()
{
Thread thread=new Thread(()->{
for (int i=1;i<=1000;i++)
{
//isInterrupted()不会改变线程的中断状态
//interrupted()会改变线程的中断状态
if(Thread.currentThread().isInterrupted()) {
try {
//调用sleep后会引发中断异常并重置中断状态为false
Thread.sleep(100);
System.out.println("在i"+i+"时中断");
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
System.out.println(i);
}
}
});
thread.start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}
中断线程的使用场景:
在某个子线程中为了等待一些特定条件的到来, 你调用了Thread.sleep(10000), 预期线程睡10秒之后自己醒来, 但是如果这个特定条件提前到来的话, 来通知一个处于Sleep的线程。又比如说.线程通过调用子线程的join方法阻塞自己以等待子线程结束, 但是子线程运行过程中发现自己没办法在短时间内结束, 于是它需要想办法告诉主线程别等我了. 这些情况下, 就需要中断.
package unit14.testInterrupt;
class Example3 extends Thread {
volatile boolean stop = false;
public static void main(String args[]) throws Exception {
Example3 thread = new Example3();
System.out.println("Starting thread...");
thread.start();
Thread.sleep(3000);
System.out.println("Asking thread to stop...");
/*
* 如果线程阻塞,将不会检查此变量,调用interrupt之后,线程就可以尽早的终结被阻
* 塞状 态,能够检查这一变量。
* */
thread.stop = true;
/*
* 这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退
* 出阻 塞的状态
* */
thread.interrupt();
Thread.sleep(3000);
System.out.println("Stopping application...");
System.exit(0);
}
public void run() {
while (!stop) {
System.out.println("Thread running...");
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
// 接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态
System.out.println("Thread interrupted...");
}
}
System.out.println("Thread exiting under request...");
}
}
/*
* 把握几个重点:stop变量、run方法中的sleep()、interrupt()、InterruptedException。串接起
* 来就是这个意思:当我们在run方法中调用sleep(或其他阻塞线程的方法)时,如果线程阻塞的
* 时间过长,比如10s,那在这10s内,线程阻塞,run方法不被执行,但是如果在这10s内,stop被
* 设置成true,表明要终止这个线程,但是,现在线程是阻塞的,它的run方法不能执行,自然也就
* 不能检查stop,所 以线程不能终止,这个时候,我们就可以用interrupt()方法了:我们在
* thread.stop = true;语句后调用thread.interrupt()方法, 该方法将在线程阻塞时抛出一个中断
* 信号,该信号将被catch语句捕获到,一旦捕获到这个信号,线程就提前终结自己的阻塞状态,这
* 样,它就能够 再次运行run 方法了,然后检查到stop = true,while循环就不会再被执行,在执
* 行了while后面的清理工作之后,run方法执行完 毕,线程终止。
* */
3.3 线程状态
新创建,可运行,被阻塞,等待,计时等待,被终止
新创建:
可运行:当线程调用start方法,从新创建转变为可运行状态。(可能并没有正在运行,需要等待系统分配cpu,以及提供运行的时间)
被阻塞:1)一个线程试图获得对象锁失败时,该线程进入阻塞状态,当其他线程释放锁,并且线程调度器允许本线程持有它时,该线程变为非阻塞状态
2)当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。(如Object.wait()或者Thread.join方法,等待Lock或Condition)
3)调用有超时参数的Thread相关的方法,调用他们使线程进入等待(Thread.sleep,Object.wait(),Thread.join,Lock.tryLock,Condition.await的计时版)
join()等待终止指定的线程
join()等待指定线程的死亡或者经过指定毫秒数
State getState() 得到指定线程的状态
3.4 守护线程
3.4.1 关于守护线程
守护线程的唯一用途是为其他线程提供服务,如:计时守护线程,垃圾回收器如果只剩下守护线程时,程序将退出。
3.4.2 守护线程的使用
通过调用t.setDaemon(true)将线程转化为守护线程
3.5 未捕获异常处理器
对于线程抛出的任何未受查异常,会在线程死亡之前,异常会被传递到一个用于未捕获异常的处理器
public class unCatchExTest {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
Thread.currentThread().setName("新线程");
System.out.println("runnable run---------------");
if(true)
{
throw new RuntimeException("一个测试的异常");
}
});
t.start();
Thread.sleep(100);
}
}
4 java多线程的同步
4.1 java多线程使用时会出现的问题
如果两个或者多个线程对同一个数据进行存储,则有可能出现错误的存取数据
public class UnsynchBankTest
{
public static final int NACCOUNTS = 100;
public static final double INITIAL_BALANCE = 1000;
public static final double MAX_AMOUNT = 1000;
public static final int DELAY = 10;
public static void main(String[] args)
{
Bank bank = new Bank(NACCOUNTS, INITIAL_BALANCE);
for (int i = 0; i < NACCOUNTS; i++)
{
int fromAccount = i;
Runnable r = () -> {
try
{
while (true)
{
int toAccount = (int) (bank.size() * Math.random());
double amount = MAX_AMOUNT * Math.random();
bank.transfer(fromAccount, toAccount, amount);
Thread.sleep((int) (10* Math.random()));
}
}
catch (InterruptedException e)
{
}
};
Thread t = new Thread(r);
t.start();
}
}
}
public class Bank
{
private final double[] accounts;
/**
* Constructs the bank.
* @param n the number of accounts
* @param initialBalance the initial balance for each account
*/
public Bank(int n, double initialBalance)
{
accounts = new double[n];
Arrays.fill(accounts, initialBalance);
}
/**
* Transfers money from one account to another.
* @param from the account to transfer from
* @param to the account to transfer to
* @param amount the amount to transfer
*/
public void transfer(int from, int to, double amount)
{
if (accounts[from] < amount) return;
System.out.print(Thread.currentThread());
//第一个线程进来,先把amount减少 。第二个线程再修改了accounts。第一个线程再完成第三步。第二个线程做了无用功
//确保线程在失去控制之前方法完成运行,那个账户就会正常
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
}
/**
* Gets the sum of all account balances.
* @return the total balance
*/
public double getTotalBalance()
{
double sum = 0;
for (double a : accounts)
sum += a;
return sum;
}
/**
* Gets the number of accounts in the bank.
* @return the number of accounts
*/
public int size()
{
return accounts.length;
}
}
上面例子总金额的减少的原因是:数据的存储并不是原子操作,如把金额转到一个账户时,需要有
1)将accounts[to]加载到寄存器
2)增加amount
3)将结果写回accounts[to]
该线程在执行过程中可能会被剥夺运行权,如果这时候第二个线程在第一个线程完成第一二步后被唤醒,并修改了同一个账户,当第一个线程被唤醒后再执行第三部时就会覆盖第二个线程的操作,导致数据错误
4.2 锁的使用
·java提供了ReentrantLock类与synchronized关键字达到防止代码块受并发访问的干扰
使用ReentrantLock类:(ReentrantLock(boolean fair)构建一个公平锁,少使用)
myLock.lock();
try{
}finally{
myLock.unLock();
}
锁写在finally中是至关重要的,他可以保证锁一定会被释放
当账户中没有足够的余额时,因为需要等待其他账户注入金额,所以需要等待,一个锁对象可以有一个或多个相关的条件对象,可以通过newCondition方法获得一个条件对象。条件对象可以用来管理那些已经进入被锁保护的代码但还不能运行的线程。因此,上面这个示例可以创建一个sufficientFunds的条件对象,并在余额不足时等待。
当一个线程调用await进入该条件的等待集时,如果这个时候锁可用,需要其他线程来唤醒该等待线程,可以通过sufficientFunds.signalAll()唤醒
public class SynchBankTest
{
public static final int NACCOUNTS = 100;
public static final double INITIAL_BALANCE = 1000;
public static final double MAX_AMOUNT = 1000;
public static final int DELAY = 10;
public static void main(String[] args)
{
Bank bank = new Bank(NACCOUNTS, INITIAL_BALANCE);
for (int i = 0; i < NACCOUNTS; i++)
{
int fromAccount = i;
Runnable r = () -> {
try
{
while (true)
{
int toAccount = (int) (bank.size() * Math.random());
double amount = MAX_AMOUNT * Math.random();
bank.transfer(fromAccount, toAccount, amount);
Thread.sleep((int) (DELAY * Math.random()));
}
}
catch (InterruptedException e)
{
}
};
Thread t = new Thread(r);
t.start();
}
}
}
public class Bank
{
private final double[] accounts;
private Lock bankLock;
private Condition sufficientFunds;
/**
* Constructs the bank.
* @param n the number of accounts
* @param initialBalance the initial balance for each account
*/
public Bank(int n, double initialBalance)
{
accounts = new double[n];
Arrays.fill(accounts, initialBalance);
//构建一个可以用来保护临界区的可重入锁
bankLock = new ReentrantLock();
sufficientFunds = bankLock.newCondition();
}
/**
* Transfers money from one account to another.
* @param from the account to transfer from
* @param to the account to transfer to
* @param amount the amount to transfer
*/
public void transfer(int from, int to, double amount) throws InterruptedException
{
bankLock.lock();
try
{
while (accounts[from] < amount)
sufficientFunds.await();//账户不够,进入等待,等待其他线程唤醒
System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
sufficientFunds.signalAll();//唤醒其他因账户金额不够陷入等待的进程
}
finally
{
bankLock.unlock();
}
}
/**
* Gets the sum of all account balances.
* @return the total balance
*/
public double getTotalBalance()
{
bankLock.lock();
try
{
double sum = 0;
for (double a : accounts)
sum += a;
return sum;
}
finally
{
bankLock.unlock();
}
}
/**
* Gets the number of accounts in the bank.
* @return the number of accounts
*/
public int size()
{
return accounts.length;
}
}