Tread构造方法,有两种重要的重载方式,由他们产生了三种主要得创建线程的方式
public Thread()
public Thread(Runnable target)
package com.wsh.object;
//1. 继承Thread类
public class FirstThread extends Thread{
private int i;
//2. 重写run方法,run方法中为线程需完成的具体任务,所以run也称作线程的执行体
@Override
public void run(){
}
public static void main(String[] args) {
//3. 创建Thread子类的实例,并调用该对象的start方法来启动该线程
new FirstThread().start();
new FirstThread().start();
//4. 也可以通过匿名内部类直接创建Thread的匿名的子类的对象来启动线程
/*new Thread(){
public void run(){
}
}.start();*/
}
}
//1.上面程序启动了三个线程,一个主线程,两个自己创建的子线程
//a. 主线程执行体为main方法的方法体
//b. 子线程的执行体为自身的run方法,这三个线程的执行体是交替运行的,不是一个运行完再运行另一个
//2.Thread的方法
//返回当前正在执行的线程对象
static Thread currentThread()
//返回调用该方法的线程对象的名字,默认主线程名为"main",用户启动的多个子线程名依次为"Thread-0","Thread-1"
getName()
//为线程对象设置名字,也可在创建线程时利用Thread(String name)构造器直接为Thread对象设置名字
setName(String name)
//3.继承Thread类创建线程时,每创建一个线程对象就需创建一个FirstThread对象,而i是基本类型实例变量,所以这种创建线程的方式无法共享线程类的基本类型实例变量,即一个线程对象i值改变,另一个线程对象的i值不跟着改变
//4.但如果该实例变量是个引用类型,Account(自定义账户类),那么两个线程的该变量可以指向同一处内存,那么其中一个修改其值,另一个中的值也改变
package com.wsh.object;
//1. 定义Runnable接口的实现类,并重写该接口的run()方法,此run方法的方法体同样也是线程的执行体
public class SecondThread implements Runnable {
private int i;
@Override
public void run() {
}
public static void main(String[] args) {
//2. 创建Runnable实现类的实例
SecondThread st = new SecondThread();
//3. 将此实例作为Thread构造器中的Runnable参数(target)来创建Thread对象,该Thread对象才是真正的线程对象,最后调用该线程对象的start方法
new Thread(st).start();
new Thread(st, "新线程2").start();
//4. 使用匿名内部类实现
/*new Thread(new Runnable() {
@Override
public void run() {
}
}, "新线程3").start();*/
}
}
//这种方式创建线程类时,Runnable对象是以参数形式(target参数)传入线程对象中的,多个线程对象是可以共享同一个target对象的,所以多个线程也可以共享同一个target对象的实例变量,即无论Runnable中定义的i是基本类型还是引用类型的实例变量,那么当一个线程对象中改变i的值,另一个的i也改变
//注意主线程中的st的i属性改变时,target的i属性不变,因为子线程中,相当于传递进去一个target对象的副本,两个子线程用的是同一个副本,和主线程不是一个target
package com.wsh.object;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//1. 实现Callable接口
public class ThirdThread implements Callable{
//2. 重写call方法
@Override
public Integer call() throws Exception {
return 0;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
ThirdThread tt = new ThirdThread();
FutureTask<Integer> task = new FutureTask<>(tt);
new Thread(task,"有返回值的线程").start();
//3. 使用匿名内部类实现
/*new Thread(new FutureTask(new Callable() {
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
return null;
}
}),"的").start();*/
System.out.println(task.get());
}
}
//1.使用Future和Callable创建线程会将call方法作为线程的执行体,且call方法可以有返回值,也可以声明抛出异常
//2.FutureTask的父接口Future中的方法
boolean cancel(boolean mayInterruptIfRunning):试图取消Future中关联的Callable任务,不会杀死线程
V get():返回Callable任务中call方法的返回值,调用该方法造成程序阻塞,即必须等子线程结束后才会获得返回值
V get(long timeout,TimeUnit unit):该方法最多阻塞timeout和unit指定的时间,如果经过指定的时间后Callable仍没有返回值,抛出TimeoutException
boolean isCancelled():如果Callable任务正常完成前取消其关联的Callable任务,则返回true
boolean isDone():如果Callable任务正常完成,返回true
等待通知:wait
收到通知:notify、notifyAll
join:也可以从运行–>阻塞,对应的进程结束后,自动从阻塞–>就绪
阻塞状态的进程只能变成就绪状态,不能直接变成运行状态
用线程对象的isAlive方法判断线程是否已经死亡,就绪、运行、阻塞状态下都会返回true,新建和死亡状态返回false,线程死亡后不会重新变成新建状态
public static void main(String[] args) throws InterruptedException {
//主线程在执行jt.join后开始阻塞,直到ft线程执行完毕,即ft.join之前,主线程与ft线程会并行执行
FirstThread ft = new FirstThread();
ft.start();
ft.join();
}
join():
join(long millis):等待millis毫秒,如果线程还没执行完成,跳过等待
join(long millis,int nanos):等待millis秒+nanos微秒,一般不用,因为系统无法精确到微秒
static void sleep(long millis):
static void sleep(long millis,int nanos):一般不用
Thread.MIN_PRIORITY:值为1
Thread.NORM_PRIORITY:值为5
Thread.MAX_PRIORITY:值为10
//若两个线程同时访问这段代码,a线程进入,判断余额大于取的钱,进入if逻辑,但马上暂停1毫秒,此时线程b被调用,由于a此时还没有对余额进行更新,b也可以进入if代码块,最后导致account虽然只有1000,但是取出了两个800
if(account.getBalance()>=drawAmount){
Thread.sleep();
account.setBalance(account.getBalance()-drawAmount);
}
//account为同步监视器,线程会获取同步监视器的锁,对于同一同步监视器,同一时间只有一个线程获取它的锁
synchronized(account){
if(account.getBalance()>=drawAmount){
Thread.sleep();
account.setBalance(account.getBalance()-drawAmount);
}
}
加锁--修改--释放锁
//实际上是调用该方法的对象(this)所关联的监视器,监视该对象的标志位
public synchronized void draw(double drawAmount){
}
package listen;
import java.util.concurrent.locks.ReentrantLock;
public class LockWu {
//同步锁这种方式,锁对象是lock对象本身,对于唯一一个LockWu对象,它只有唯一一个lock对象,所以对于唯一一个X对象(该对象调用method),只有一个线程可以获取该锁
private final ReentrantLock lock = new ReentrantLock();
public void method(){
try{
System.out.println("这段代码可以一起执行");
lock.lock();
//需要保证线程安全的代码,例如取钱
Thread.sleep(1000);
System.out.println("LockWu,只有一个线程可以执行我");
}catch (Exception e){
;
}finally {
//一般使用finally块来确保锁一定被释放
lock.unlock();
}
}
}
package listen;
public class ThreadWu extends Thread{
private LockWu lockwu;
public ThreadWu(LockWu lockwu){
super();
this.lockwu=lockwu;
}
@Override
public void run() {
lockwu.method();
}
public static void main(String[] args) {
LockWu lockwu = new LockWu();
new ThreadWu(lockwu).start();
new ThreadWu(lockwu).start();
new ThreadWu(lockwu).start();
}
}
//如果有线程已获取该锁,则返回false,防止单笔业务重复提交(如定时任务)
tryLock()
//如果已有线程获取该锁的方法,等待timeout个TimeUnit,如果之后当前线程仍无法获取该锁,则返回false,可以防止由于处理不当导致的阻塞(大家都在等待该锁,导致线程队列溢出),
tryLock(long timeout, TimeUnit unit)
//该方法也是加锁,但如果调用线程对象的interrupt方法,线程中调用该方法会处,会立即释放该线程获取的锁,如果线程还未能获取到该锁,也会停止对锁的等待,最后并抛出InterruptedException,这样可以防止不正常操作长时间占用锁
lockInterruptibly()
public void A(){
lock.lock();
//一个线程,可以对已被加锁的ReentrantLock锁,再次加锁,ReentrantLock对象会维持一个计数器来追踪lock()方法的嵌套调用
//即一段被锁保护的代码中可以调用另一个被相同锁保护的方法,该锁就叫可重入锁,不可重入锁不能调用被相同锁保护的方法,因为会发生死锁,synchronized也是可重入锁
B();
lock.unlock();
}
public void B(){
lock.lock();
lock.unlock();
}
class A{
public synchronized void foo(B b){
Thread.sleep(200);
b.last();
}
public synchronized void last(){
}
}
class B{
public synchronized void bar(A a){
Thread.sleep(200);
a.last();
}
public synchronized void last(){
}
}
public static void main(String[] args) throws InterruptedException {
A a = new A();
B b = new B();
//同时启动两个线程,分别执行a.foo(b)和b.bar(a),a.foo(b)先对a进行加锁,然后等待0.2s,然后b.bar(a)开始执行,对b加锁,并等待0.2s,之后a.foo(b)中的b.last()开始执行,想获取b的锁,但b已被加锁,只能等待,同理b.bar(a)中a.last()想对a加锁,却发现a已被加锁,此时两个线程互相等待对方释放锁,造成死锁
new Thread(){
@Override
public void run(){
try {
a.foo(b);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
new Thread(){
@Override
public void run(){
try {
b.bar(a);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
}
程序通常无法准确控制线程的轮换执行,java提供一些机制保证线程协调运行
//导致当前线程等待,并释放当前线程对该同步监视器的锁,直到其他线程调用该同步监视器的notify或notifyAll方法唤醒该线程
wait()、wait(long a)
//唤醒等待此同步监视器的锁的单个线程,如果有多个线程等待,任意唤醒其中一个,一个线程只有被wait后才可以被notify
notify()
//唤醒所有等待此同步监视器的锁的线程
notifyAll()
//notify、notifyAll区别:对于notify方法,当多个线程在等待时,其中一个线程A被唤醒后,如果A中不再调用notify方法,则其他等待的线程永远不会被唤醒,对于notifyAll:所有线程都被唤醒,其中一个获得同步监视器的锁,该线程执行完毕后,其他线程不用再被唤醒,直接随机获取锁并执行。一个线程只有被唤醒才有可能重新获取锁,没有被唤醒就一定无法获取锁,wait后的线程在等待被唤醒而不是在等待锁,notifyAll后所有等待被唤醒的线程开始都变为等待锁
await()
signal()
signalAll()
void put(E e)
E take()
线程组可以对一批线程分类管理,用户创建的所有线程都属于指定的线程组,如果没有显式指定线程组,则该线程属于默认线程组,默认情况下子线程和创建他们的父线程处于同一个线程组。一个线程加入某线程组后一直属于该线程组,直到该线程死亡。线程运行中途不能改变其所属的线程组
Thread(ThreadGroup group,Runnable target)
Thread(ThreadGroup group,Runnable target,String name)
Thread(ThreadGroup group,String name)
//由于线程不能改变其所属线程组,所以没有setThreadGroup方法
ThreadGroup getThreadGroup()
ThreadGroup(String name)
//以name为名,parent为父线程组来创建线程组
ThreadGroup(ThreadGroup parent,String name)
//获取线程组的名,不允许修改
getName()
//返回线程组中活动线程的数目
int activeCount()
//中断该线程组中所有线程
interrupt()
判断是否为后台线程组
isDaemon()
//设置为后台线程组,后台线程组中最后一个线程结束后或被销毁后,后台线程组自动销毁
setDaemon()
//设置线程组中线程的最高优先级
setMaxPriority(int pri)
//为线程对象设置异常处理器
setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
//为线程类的所有线程对象设置默认的异常处理器
static setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
public class ThreadGroupTest extends ThreadGroup{
@Override
public void unCaughtException(Thread t,Throwable e){
}
}
线程池在系统启动时即刻创建大量空闲线程,程序将一个Runnable或Callable对象传给线程池,线程池会启动一个线程执行他们的call或run方法,当run、call方法结束后,线程不会立即死亡,而是再次返回线程池中成为空闲状态,等待下一个run或call方法执行。除此之外,使用线程池可以利用其最大线程参数,有效控制并发线程数,因为系统中如果包含大量并发线程时,会导致系统性能急剧下降,甚至导致jvm崩溃
//创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会缓存在线程池中
ExecutorService newCachedThreadPool()
//创建一个可重用的具有固定线程数的线程池
ExecutorService newFixedThreadPool(int nThreads)
//相当于newFixedThreadPool(1);
ExecutorService newSingleThreadExecutor()
//创建具有指定线程数的线程池,它可以在指定延迟后执行线程任务,corePoolSize为线程池中保存的线程数,即使线程空闲,也被保存在线程池中
ScheduledExecutorService newScheduledThreadPool(int ccorePoolSize)
//相当于newScheduledThreadPool(1)
ScheduledExecutorService newSingleThreadScheduledExecutor()
//将Runnable对象交给指定线程池,线程池在空闲时执行Runnable对象代表的任务,其中Future对象代表Runnable任务的返回值,由于run方法没有返回值,所以Future对象在run方法结束后返回null。但可以调用Future的isDone()、isCancelled方法来获得Runnable对象的执行状态
Future<?> submit(Runnable task)
//系统内部会将Runnable对象转为一个Callable对象,result就是Callable中call方法返回值
<T>Future<T> submit(Runnable task,T result)
//Future抽象为Callable对象里call方法返回,其get()方法可以得到call方法真正返回值
<T>Future<T> submit(Callable<T> task)
//指定callable任务在delay个unit延迟后开始执行
ScheduledFuture<V> schedule(Callable<V> callable,long delay,TimeUnit unit)
ScheduledFuture<?> schedule(Runnable command,long delay,TimeUnit unit)
//command在initialDelay后开始执行,以后每隔period后重复执行
ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)
//command任务在initialDelay后开始执行,以后每一次执行结束和下一次执行开始之间都有dealy延迟,如果任务在一次执行时遇到异常,会取消后续执行,否则只能通过程序显式取消或终止该任务
scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit)
ExecutorService pool = Executors.newFixedThreadPool(6);
Runnable target = new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
}
};
pool.submit(target);
pool.submit(target);
pool.shutdown();
//向ThreadLocal的对象中存放值,以当前线程对象作为key,value作为value,存入ThreadLocal.ThreadLocalMap中,从而保证不同的线程对象会对应不同的value
void set(T value)
//返回ThreadLocal.ThreadLocalMap中key值为当前线程对象的value值
T get()
//删除ThreadLocal.ThreadLocalMap中以当前线程对象为key的键值对
void remove()