公司动态

Java线程(1)

📅 2026/8/19 1:02:10
Java线程(1)
1、创建线程的三种方法①、创建Thread类的子类对象实现run()方法run()是函数式接口Runnable中的抽象方法Thread类实现了Runnable接口调用Thread对象的start()来启动线程线程会执行run()方法public class Test extends Thread { Test() { super(Thread name); } public void run()//run为Thread中的线程方法 { String strThreadName getName(); System.out.println(strThreadName thread execute); } public static void fun1() throws Exception { //定义一个Thread对象只启动一个线程一个Thread对象代表一个线程 Test t new Test(); t.start(); //调用start()启动线程 t.start(); //error再次调用start()会抛出异常一个Thread对象不能启动两个线程 t.join(); } public static void fun2() { //定义两个Thread对象启动两个线程因为是两个Thread对象所以线程函数中访问的是各自的实例变量各线程不能共享自己的实例变量 new Test().start(); new Test().start(); } public static void main(String[] args) throws Exception { fun1(); fun2(); } }public class Test { public static void main(String[] args) throws Exception { Thread t new Thread() { Override public void run() { System.out.println(thread run); } }; t.start(); t.join(); } }②、创建实现Runnable接口的类的对象将该对象赋给Thread调用Thread对象的start()来启动线程线程会执行run()方法如下所示。这种方法使多个线程可以共享同一个线程执行类对象Runnable子类对象适合多线程处理同一资源的情况。示例1class ThreadRunner implements Runnable { public ThreadRunner(String str) { _str str; } private String _str; public void run() //run为Runnable中的线程方法 { System.out.println(_str); } } public class Test { public static void main(String[] args) { ThreadRunner st new ThreadRunner(new String(ThreadRunner Test)); //定义两个Thread对象启动两个线程因为各线程使用的是一个Runnable对象所以可以共享该对象的实例变量 new Thread(st).start(); new Thread(st , 线程名称).start(); } }示例2public class Test { public static void main(String[] args) { new Thread(()-{System.out.println(Thread execute);}).start(); new Thread(()-{System.out.println(hello world);},线程名称).start(); } }可以在线程里直接引用局部对象和成员对象public class Foo { public void func(){ int n 100; new Thread(()-{ System.out.println(n); System.out.println(m_n); }).start(); } public int m_n 99; }③、实现Callable接口使用FutureTask对象来包装该Callable对象以该FutureTask对象作为Thread类的target来创建线程这种方式是以Callable接口的call方法来作为线程的执行体优点是call方法可以有返回值还可以声明抛出异常。类似RunnableCallable只包含一个抽象方法call所以它也是一个函数式接口可以使用Lambda表达式来创建Callable对象。Callable接口的泛型类型应该与call方法返回值类型相同。FutureTask对象之所以可以作为Thread类的target是因为FutureTask类实现了Runnable接口在Runnable的run()中会调用Callable的call()方法。FutureTask还实现了Future接口Future代表线程结束后的返回值。Future接口中包含了一些控制它关联的Callable任务的方法如get获得Callable任务里call方法的返回值、cancel试图取消关联的Callable任务、isCancelled关联的Callable任务在正常完成前是否被取消、isDone关联的Callble任务是否已完成。import java.util.concurrent.*; public class Test { public static void main(String[] args) { // 使用Lambda表达式创建CallableInteger对象 // 使用FutureTask来包装Callable对象 /* CallableInteger call (CallableInteger)() - { int i 0; for ( ; i 100 ; i ) { System.out.println(Thread.currentThread().getName() 的循环变量i的值 i); } // call()方法可以有返回值 return i; }; FutureTaskInteger task new FutureTaskInteger(call); */ FutureTaskInteger task new FutureTaskInteger((CallableInteger)() - { int i 0; for ( ; i 100 ; i ) { System.out.println(Thread.currentThread().getName() 的循环变量i的值 i); } // call()方法可以有返回值 return i; }); //以FutureTask对象作为Thread的target实质还是以Callable对象来创建、并启动线程 new Thread(task , 有返回值的线程).start(); try { // get方法可以获取线程函数返回值该方法可能会抛出异常并且为阻塞等待线程结束 System.out.println(子线程的返回值 task.get()); } catch (Exception ex) { ex.printStackTrace(); } } }2、线程状态和Thread中的方法与C不同Java中主线程结束后其创建的子线程仍然能运行但如果子线程是守护线程的话那么主线程结束后子线程也会结束。JVM启动后不只有一个主线程还会有垃圾收集、内存管理等线程。线程的状态阻塞Blocked调用了sleep()、wait()等阻塞方法、调用suspend()。就绪Runnable调用了Thread线程对象的start()方法等待调度器执行该线程sleep()时间到阻塞方法返回调用resume()调用yield()。运行Running被调度器调度得到CPU时间。死亡run()/call()返回、调用stop()、Exception或error。Thread中的方法静态方法currentThread()获得当前Thread线程对象。成员方法getName()/setName()获得/设置线程名默认主线程名为main子线程名为Thread-n。成员方法join()/join(long millis)等待线程结束。成员方法isAlive()线程已死亡的话返回false否则返回true。成员方法setDaemon()设置为后台线程守护线程应该在start()之前调用该方法当前台线程都死亡后后台线程自动死亡。默认从Daemon线程产生的线程也是Daemon线程。静态方法sleep()睡眠执行毫秒或微妙该方法声明抛出异常所以调用它时要么捕获该异常要么显示声明抛出该异常。成员方法interrupt()一个进入Blocked状态的线程可以由另一个线程调用该线程的interrupt()让它离开Blocked状态而且此时线程会抛出InterruptedException异常该异常是受检异常必须对其捕获处理。比如一个进入sleep()的线程其它线程调用该线程的interrupt()可以唤醒它public class Test { public static void main(String[] args) { Thread thread new Thread() { Override public void run() { try { Thread.sleep(99999); }catch(InterruptedException ex){ System.out.println(I woke up); throw new RuntimeException(ex); } } }; thread.start(); thread.interrupt(); } }静态方法yield()让运行状态的线程转入就绪状态使当前线程暂停一下与当前线程优先级相同或更高的其它就绪线程会获得执行机会。setPriority()/getPriority()设置/获得线程的优先级可以有十档优先级默认是5Thread.NORM_PRIORITY推荐使用Thread类的静态常量MAX_PRIORITY(10)、MIN_PRIORITY(1)。线程默认与创建其的父线程优先级相同可以使用优先级高的线程将会得到更多的执行机会。成员方法suspend()挂起线程不推荐使用。成员方法resume()恢复挂起的线程不推荐使用。成员方法stop()结束线程不推荐使用。3、线程同步①、同步代码块——使用synchronized(obj){ ... }同步监视器为一个obj对象线程进入同步代码块之前先获得对于同步监视器的锁定多个线程使用同一个同步监视器同步代码块执行结束或代码块中break、return终止了代码块的执行或出现Exception或error的时候当前线程释放释放同步监视器。需要注意的一点是如果线程执行当中调用了同步监视器obj对象的wait()方法那么也会释放同步监视器。Object的wait()是阻塞方法会导致当前线程暂停另一线程可以调用对象的notify()或notifyAll()来唤醒暂停的线程。如下所示的示例程序会在5秒钟后打印main thread然后才会打印sub threadclass Foo { Integer m_lock 0; public void fun() { Thread t new Thread() { Override public void run() { try { Thread.sleep(1000); }catch(Exception ex) { ex.printStackTrace(); } synchronized(m_lock) { System.out.println(sub thread); } } }; t.start(); synchronized(m_lock) { try { Thread.sleep(5000); }catch(Exception ex) { ex.printStackTrace(); } System.out.println(main thread); } } } public class Test { public static void main(String[] args) { Foo f new Foo(); f.fun(); } }下面为使用synchronized(obj){ ... }同步代码块来实现购票程序class TicketAccount { private int balance 1; //余票 public int getBalance(){return balance;} //查询余票 public void setBalance(int buyCnt) {balance - buyCnt;}//购票 } class BuyTicket implements Runnable { private TicketAccount account; private int buyCnt 1; //购票数 BuyTicket(TicketAccount account) { this.account account; } public void run() { synchronized(account) { if(account.getBalance() buyCnt) //余票足够的话就购票 { account.setBalance(buyCnt); System.out.println(Thread.currentThread().getName() 购票成功余票为 account.getBalance()); } else { System.out.println(Thread.currentThread().getName() 购票失败无票); } } } } public class Test { public static void main(String[] args)throws Exception { BuyTicket ticket new BuyTicket(new TicketAccount()); //开启两个购票的线程 Thread t1 new Thread(ticket, 线程1); t1.start(); Thread t2 new Thread(ticket, 线程2); t2.start(); t1.join(); t2.join(); } }②、同步方法——使用synchronized此时的同步监视器为被synchronized修饰的方法的this实例如下所示的两个方法是相同的效果public synchronized void func() { ...... } public void func() { synchronized(this) { ...... } }下面是使用synchronized声明同步方法实现购票的示例class BuyTicket { private int balance 1; //余票 private int getBalance(){return balance;} //查询余票 private void setBalance(int buyCnt) {balance - buyCnt;}//购票 public synchronized void buyTicket(int buyCnt) { if(getBalance() buyCnt) //余票足够的话就购票 { setBalance(buyCnt); System.out.println(Thread.currentThread().getName() 购票成功余票为 getBalance()); } else { try{ wait(); } catch(Exception e) { } System.out.println(Thread.currentThread().getName() 购票失败无票); } } } class BuyTicketThread implements Runnable { private BuyTicket account; private int buyCnt 1; //购票数 BuyTicketThread(BuyTicket account) { this.account account; } public void run() { account.buyTicket(buyCnt); } } public class Test { public static void main(String[] args)throws Exception { BuyTicketThread ticket new BuyTicketThread(new BuyTicket()); //开启两个购票的线程 Thread t1 new Thread(ticket, 线程1); t1.start(); Thread t2 new Thread(ticket, 线程2); t2.start(); t1.join(); t2.join(); } }③、使用同步锁Lock同步锁、ReadWriteLock读写锁是两个接口ReentrantLock和ReentrantReadWriteLock是它们的实现类并且提供可重入可以对已加锁的Lock再次加锁。Lock中还定义了tryLock()方法获得锁失败会直接返回false而不是阻塞等待。为了避免程序发生异常而无法解锁Lock一定要在finally中调用Lock对象的unlock()方法解锁。import java.util.concurrent.locks.*; class BuyTicket { private int balance 1; //余票 private final ReentrantLock lock new ReentrantLock(); private int getBalance(){return balance;} //查询余票 private void setBalance(int buyCnt) {balance - buyCnt;}//购票 public void buyTicket(int buyCnt) { lock.lock(); try { if(getBalance() buyCnt) //余票足够的话就购票 { setBalance(buyCnt); System.out.println(Thread.currentThread().getName() 购票成功余票为 getBalance()); } else { System.out.println(Thread.currentThread().getName() 购票失败无票); } } finally { lock.unlock(); } } } class BuyTicketThread implements Runnable { private BuyTicket account; private int buyCnt 1; //购票数 BuyTicketThread(BuyTicket account) { this.account account; } public void run() { account.buyTicket(buyCnt); } } public class Test { public static void main(String[] args)throws Exception { BuyTicketThread ticket new BuyTicketThread(new BuyTicket()); //开启两个购票的线程 Thread t1 new Thread(ticket, 线程1); t1.start(); Thread t2 new Thread(ticket, 线程2); t2.start(); t1.join(); t2.join(); } }使用读写锁的好处是当只有读操作的时候所有线程都可以获得锁当写的时候会加锁。读写锁适合有两个或两个以上线程会进行读操作的情况。调用ReentrantReadWriteLock.ReadLock().lock()时若没有任何ReentrantReadWriteLock.().Writelock().lock()也就是没有任何写入获得锁定的时候就可以直接获得锁。调用ReentrantReadWriteLock.().Writelock().lock()时若没有任何ReentrantReadWriteLock.ReadLock().lock()或ReentrantReadWriteLock.().Writelock().lock()也就是没有任何读取或写入获得锁定的时候就可以直接获得锁。读写锁使用示例如下import java.util.concurrent.locks.*; class CFoo { void read() { rwLock.readLock().lock(); try { //ToDo... }finally { rwLock.readLock().unlock(); } } void write() { rwLock.writeLock().lock(); try { //ToDo... }finally { rwLock.writeLock().unlock(); } } ReadWriteLock rwLock new ReentrantReadWriteLock(); }ReadWriteLock在没有任何读取或写入锁定的时候才会获取写入的锁定悲观锁当读取线程很多写入线程很少的情况使用ReadWriteLock进行写入锁定的话可能会使写入线程遭受饥饿问题也就是写入线程迟迟无法竞争到锁因为很多读线程在占用锁而长时间处于等待状态。StampedLock提供了乐观的读取操作乐观锁也就是在读取线程很多写入线程很少的情况下可以乐观的认为读取和写入同时发生的机会很少读取的时候可以先不用锁定在读入数据后再判断数据是否已经遭到写入线程的变更再进行后续的措施重新读取变更的数据或抛出异常。Java 8增加的StampedLock可以在大多数场景中替换掉原来的ReentrantReadWriteLock。以下为使用StampedLock的示例import java.util.concurrent.locks.*; class CFoo { Object get(int index) { long stamp lock.tryOptimisticRead(); //试着乐观的获得读锁定该方法不会真正的获得读锁定不会阻塞调用它来获得一个锁定的标记 Object elem elems[index]; //直接读取数据 //validat()判断锁定的标记是不是已经被其它排他锁获得了是的话返回false表示数据可能已经被改变需要加锁获得数据 //validat()返回true表示上面获取数据过程中没有其它的写入操作可以直接使用读取的数据 if(!lock.validate(stamp)) { stamp lock.readLock(); //真正的获得读锁定 try { elem elems[index]; }finally { lock.unlockRead(stamp); //释放读锁定 } } return elem; } void add(Object elem) { long stamp lock.writeLock(); //获得写入锁定 try { elems.add(elem); }finally { lock.unlock(stamp); //释放写入锁定 } } StampedLock lock new StampedLock(); Object[] elems; }死锁当两个线程互相等待对方释放同步监视器时就会发生死锁。如下程序中子线程调用了b对象的同步方法bar()所以锁定了同步监视器b主线程调用了a对象的同步方法foo()所以锁定了同步监视器a而当子线程的bar()方法中要调用a对象的last()方法时就需要先对a对象加锁而此时a对象正被主线程锁定主线程的foo()方法中也想调用b对象的last()方法这就造成了互相等待的死锁。class A { public synchronized void foo( B b ) { try { Thread.sleep(200); } catch(InterruptedException ex) { ex.printStackTrace(); } b.last(); //企图调用B实例的last()方法 } public synchronized void last() { System.out.println(进入了A类的last()方法内部); } } class B { public synchronized void bar( A a ) { try { Thread.sleep(200); } catch(InterruptedException ex) { ex.printStackTrace(); } a.last(); //企图调用A实例的last()方法 } public synchronized void last() { System.out.println(进入了B类的last()方法内部); } } public class DeadLock implements Runnable { A a new A(); B b new B(); public void init() { a.foo(b); } public void run() { // 调用b对象的bar方法 b.bar(a); } public static void main(String[] args) { DeadLock dl new DeadLock(); new Thread(dl).start(); // 调用a对象的foo方法 dl.init(); } }④、原子类AtomicXXX是原子类比如AtomicInteger、AtomicLong、AtomicReference(对象的引用类型)等它们支持计算的原子操作而不用再加锁如计算可以直接调用原子类的aincrementAndGet()方法而不用加锁。AtomicDouble不属于java标准中类型可以引入google.guava来使用它如下为其依赖groupIdcom.google.guava/groupId artifactIdguava/artifactId version26.0-jre/versionAtomicXXX原子类利用了CAScompareAndSwap技术来实现旋转锁。比如当一个线程对一个数n调用aincrementAndGet()的时候会先获得n的值然后比较n现在在内存中的值是否还等于刚才获得的n的值如果等于的话就1不等于的话循环上面的操作直到等于的情况。比如线程A调用aincrementAndGet()在CAS方法中获得了n的值为100然后线程B执行aincrementAndGet()在CAS中也获得了n的值为100然后A的CAS继续比较n内存中的值为100与刚才获得的值相同所以将n加1,A的CAS执行成功 而线程B的CAS继续执行的时候比较n内存中的值为101与刚才获得的n的值不同所以不执行加1操作CAS返回失败而B会再次进行CAS操作直到成功。注意CAS利用了现代CPU的技术来使比较操作和加1操作是一个不可分割的原子操作。CAS实现的旋转锁的优点无锁编程如果使用lock等锁的话就是在访问共享变量前不管该变量有没有被其它线程使用都先加锁后访问加锁是一个耗费CPU的动作CAS不使用锁但它其实相当于是一个乐观锁如果变量被其它线程改变会直接返回失败而且失败的话会一直尝试CompareAndSwap直到成功否则是是直接对变量进行操作效率高。CAS实现的旋转锁的缺点CAS在意识到变量被其它线程修改后会返回失败然后再次进行比较和赋值的操作直到成功这就是自旋如果长时间不成功的话也会给CPU带来高额开销。4、线程通信①、使用Object的成员方法wait()、notify()、notifyAll()这三个方法必须由synchronized中的同步监视器对象来调用当线程中调用了同步监视器对象的wait()后会释放同步监视器并等待当调用了同步监视器对象的notify()后会从对象等待集合中随机通知一个线程。对于使用synchronized修饰的同步方法因为该类的默认实例this就是同步监视器所以可以在同步方法中直接调用。 对于synchronized(obj)修饰的同步代码块因为同步监视器是一个obj对象所以必须使用该对象来调用这三个方法。class Operation { boolean bFlag false; public synchronized void waitFun() { try { System.out.println(Thread.currentThread().getName() wait...); while(!bFlag) //这里使用while循环而不是if是因为wait()的返回可能是因为线程的私自苏醒 wait(); System.out.println(Thread.currentThread().getName() run!); } catch(InterruptedException ex) { ex.printStackTrace(); } } public synchronized void notifyFun() { System.out.println(Thread.currentThread().getName() notify.); bFlag true; notify(); } } class WaitThreadRun implements Runnable { private Operation op; public WaitThreadRun(Operation op){this.op op;} public void run() { op.waitFun(); } } class NotifyThreadRun implements Runnable { private Operation op; public NotifyThreadRun(Operation op){this.op op;} public void run() { op.notifyFun(); } } public class Test { public static void main(String[] args)throws Exception { Operation op new Operation(); Thread t1 new Thread(new WaitThreadRun(op), 等待线程); t1.start(); Thread.sleep(1000); Thread t2 new Thread(new NotifyThreadRun(op), 通知线程); t2.start(); t1.join(); t2.join(); } }②、使用Condition类的await()、signal()、signalAll()对于使用Lock锁来同步的程序不存在同步监视器可以使用Condition来控制线程通信在这种情况下Lock替代了同步代码块或方法Condition替代了同步监视器的功能。Condition的三个方法必须在获得同步锁Lock后使用线程调用Contidion的await()后会放弃对Lock的锁定。import java.util.concurrent.locks.*; class Operation { bool bStart false; private final ReentrantLock lock new ReentrantLock(); private final Condition cond lock.newCondition(); public void waitFun() { lock.lock(); try { System.out.println(Thread.currentThread().getName() wait...); while(!bStart) cond.await(); System.out.println(Thread.currentThread().getName() run!); } catch(InterruptedException ex) { ex.printStackTrace(); } finally { lock.unlock(); } } public void notifyFun() { lock.lock(); System.out.println(Thread.currentThread().getName() notify.); bStart true; cond.signal(); lock.unlock(); } } class WaitThreadRun implements Runnable { private Operation op; public WaitThreadRun(Operation op){this.op op;} public void run() { op.waitFun(); } } class NotifyThreadRun implements Runnable { private Operation op; public NotifyThreadRun(Operation op){this.op op;} public void run() { op.notifyFun(); } } public class Test { public static void main(String[] args)throws Exception { Operation op new Operation(); Thread t1 new Thread(new WaitThreadRun(op), 等待线程); t1.start(); Thread.sleep(1000); Thread t2 new Thread(new NotifyThreadRun(op), 通知线程); t2.start(); t1.join(); t2.join(); } }下面是使用条件变量实现生产者-消费者模型的例子import java.util.concurrent.locks.*; class Clerk { private int product -1; private Lock lock new ReentrantLock(); private Condition condition lock.newCondition(); public void setProduct(int product) throws InterruptedException //生产者 { lock.lock(); try { waitIfFull(); this.product product; System.out.printf(生产者设定%d%n, this.product); condition.signal(); }finally { lock.unlock(); } } private void waitIfFull() throws InterruptedException { while(this.product ! -1) condition.await(); } public int getProduct() throws InterruptedException //消费者 { lock.lock(); try { waitIfEmpty(); int p this.product; this.product -1; System.out.printf(消费者取走%d%n, p); condition.signal(); return p; }finally{ lock.unlock(); } } private void waitIfEmpty() throws InterruptedException { while(this.product -1) condition.await(); } } class Producer implements Runnable { private Clerk clerk; public Producer(Clerk clerk) { this.clerk clerk; } Override public void run() { System.out.println(生产者开始生产整数...); for(int i 0; i 10; i) { try { clerk.setProduct(i); }catch(InterruptedException ex) { throw new RuntimeException(ex); } } } } class Consumer implements Runnable { private Clerk clerk; public Consumer(Clerk clerk) { this.clerk clerk; } Override public void run() { System.out.println(消费者开始消费整数...); for(int i 0; i 10; i) { try { clerk.getProduct(); }catch(InterruptedException ex) { throw new RuntimeException(ex); } } } } public class Test { public static void main(String[] args) { Clerk clerk new Clerk(); new Thread(new Producer(clerk)).start(); new Thread(new Consumer(clerk)).start(); } }上面的示例可能会存在一个问题那就是在有多个生产者、消费者线程的时候调用notify()时可能通知的是生产者也可能通知的是消费者可以使用多个Condition实例一个给生产者线程用一个给消费者线程用class Clerk { private int product -1; private Lock lock new ReentrantLock(); private Condition producerCond lock.newCondition(); private Condition consumerCond lock.newCondition(); public void setProduct(int product) throws InterruptedException //生产者 { lock.lock(); try { waitIfFull(); this.product product; System.out.printf(生产者设定%d%n, this.product); consumerCond.signal(); }finally { lock.unlock(); } } private void waitIfFull() throws InterruptedException { while(this.product ! -1) producerCond.await(); } public int getProduct() throws InterruptedException //消费者 { lock.lock(); try { waitIfEmpty(); int p this.product; this.product -1; System.out.printf(消费者取走%d%n, p); producerCond.signal(); return p; }finally{ lock.unlock(); } } private void waitIfEmpty() throws InterruptedException { while(this.product -1) consumerCond.await(); } }③、使用阻塞队列BlockingQueueBlockingQueue是Queue容器的子接口当生产者线程调用put()方法向其队尾插入元素的时候如果队列已满则当前线程会阻塞当消费者线程调用take()方法从队列头取出元素的时候如果队列为空的话当前线程会阻塞。Queue中在队列尾插入元素的方法有add队列满会抛出异常、offer队列满会返回false在队列头部删除并返回删除的元素有remove队列空会抛出异常、poll队列空会返回false在队列头部取出但不删除元素有element队列空会抛出异常、peek队列空会返回false。BlockingQueue下有以下5个实现类ArrayBlockingQueue基于数组实现的BlockingQueue。LinkedBlockingQueue基于链表实现的BlockingQueue。PriorityBlockingQueue该队列以元素大小排列最小元素排在队头元素大小判断可以通过元素实现Comparable接口本身大小来自然排序也可以使用一个Comparator比较器来定制排序。SynchronousQueue同步队列对该队列的存、取操作必须交替进行。DelayQueue底层基于PriorityBlockingQueue它要求元素类型实现Delay接口根据接口方法的返回值进行排序。