Java 多线程学习(一)
目录
一、基本概念
二、线程的创建和使用
三、线程的生命周期
四、线程的同步
五、其他
一、基本概念
程序:一段静态的代码。
进程:正在运行的一个程序。
线程:线程作为调度和执行的单位,每个拥有独立运栈序计数器。
单核 CPU 和多核 CPU的理解?
单核 CPU ,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU就好比收费人员。
如果是多核的话,相当于多个收费人员同时收费。才能更好发挥线程效率。现在电脑都是多核的。
windows:cpu核数查看
双击打开C:\Windows\System32\wbem\WMIC.exe
敲命令:cpu get numberofcores

NumberOfCores:4核
JAVA代码获取CPU核数
// cpu数目
int cpu = Runtime.getRuntime().availableProcessors();
并行与并发?
并行:多个CPU同时执行多个任务。
并发:一个CPU同时执行多个任务。
多线程的优点:
1.提高应用程序的响应
2.提高计算机系统CPU的利用率
何时需要多线程?
1.程序需要同时执行两个或多个任务。
2.程序需要实现一些需要等待的任务时。
线程的分类
Java中的线程分为两类:一种是守护线程,一种是用户线程。
1.唯一区别是:判断JVM何时离开
2.守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
3.Java垃圾回收就是一个典型的守护线程。
4.若JVM中都是守护线程,当前JVM将退出。
二、线程的创建和使用
创建线程四种方式:
1.继承Thread类的方式
2.实现Runnable接口的方式
3.实现Callable接口的方式(JDK5.0后)
4.使用线程池的方式(JDK5.0后,工作中推荐使用)
方式1:继承Thread类的方式
/*** 例子:遍历100以内的所有的偶数*/
public class ThreadTest1 {public static void main(String[] args) {SubThread t1=new SubThread();
// t1.run();//不能通过直接调用run()的方式启动线程t1.start();//通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
// t1.start();//再启动一个线程,不可以还让已经start()的线程去执行。会报IllegalThreadStateException异常}
}class SubThread extends Thread {//重写Thread类的run()@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i % 2 == 0){System.out.println(Thread.currentThread().getName() + ":" + i);}}}
}
方式2:实现Runnable接口的方式
/*** 例子:遍历100以内的所有的偶数*/
public class ThreadTest2 {public static void main(String[] args) {SubThread2 r = new SubThread2();//创建实现类的对象Thread t1=new Thread(r);//将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象t1.setName("线程1");t1.start();//再启动一个线程Thread t2=new Thread(r);t2.setName("线程2");t2.start();}
}class SubThread2 implements Runnable {@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i % 2 == 0){System.out.println(Thread.currentThread().getName() + ":" + i);}}}
}
方式3:实现Callable接口的方式(JDK5.0后)
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;/*** 例子:遍历100以内的所有的偶数,返回求和*/
public class ThreadTest3 {public static void main(String[] args) {SubThread3 c=new SubThread3();//创建Callable接口实现类的对象FutureTask futureTask = new FutureTask(c);//将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象Thread t1=new Thread(futureTask);//将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()t1.start();try {//获取Callable中call方法的返回值//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。Object sum = futureTask.get();System.out.println("总和为:" + sum);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}
}class SubThread3 implements Callable {//实现call方法,将此线程需要执行的操作声明在call()中@Overridepublic Object call() throws Exception {int sum = 0;for (int i = 1; i <= 100; i++) {if(i % 2 == 0){System.out.println(i);sum += i;}}return sum;}
}
方式4:使用线程池的方式(JDK5.0后,工作中推荐使用)
JDK5.0起提供了线程池相关API:ExecutorService和Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command):执行任务 /命令,没有返回值,一般用来执行Runnable
Future
void shutdown():关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool();创建一个可缓存的线程池
Executors.newFixedThreadPool(n);创建固定数目线程的线程池
Executors.newSingleThreadExecutor();创建一个单线程化的Executor。
Executors.newScheduledThreadPool(n);调度型线程池,定时/周期性执行
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;/*** 使用线程池* * 好处:* 1.提高响应速度(减少了创建新线程的时间)* 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)* 3.便于线程管理* corePoolSize:核心池的大小* maximumPoolSize:最大线程数* keepAliveTime:线程没有任务时最多保持多长时间后会终止*/
public class ThreadTest4 {public static void main(String[] args) {//提供指定线程数量的线程池ExecutorService service = Executors.newFixedThreadPool(10);
// ExecutorService service = Executors.newCachedThreadPool();//线程池的实际实现类 ThreadPoolExecutorSystem.out.println(service.getClass());//执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象service.execute(new NumberThread());//适合适用于Runnableservice.execute(new NumberThread1());//适合适用于Runnable
// service.submit(Callable callable);//适合使用于CallableThreadPoolExecutor service1 = (ThreadPoolExecutor) service;//设置线程池的属性service1.setCorePoolSize(15);
// service1.setKeepAliveTime();//3.关闭连接池service.shutdown();}
}class NumberThread implements Runnable{@Overridepublic void run() {for(int i = 0;i <= 100;i++){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}if(i % 2 == 0){System.out.println(Thread.currentThread().getName() + ": " + i);}}}
}class NumberThread1 implements Runnable{@Overridepublic void run() {for(int i = 0;i <= 100;i++){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}if(i % 2 != 0){System.out.println(Thread.currentThread().getName() + ": " + i);}}}
}
三、线程的生命周期
JDK中Thread.State类中定义了线程的几种状态,如上图。
新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源。
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能。
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态。
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束。
四、线程的同步
多线程同时操作共享数据导致最终数据不整合的线程安全问题。
解决办法:Java中的同步机制来解决线程的安全问题(好处)
局限性:操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
三种同步机制方式:
1.同步代码块
2.同步方法
3.Lock(锁)(JDK5.0后)
方式1:同步代码块
synchronized(同步监视器){//需要被同步的代码
}
说明:1.操作共享数据的代码,即为需要被同步的代码。-->不能包含代码多了,也不能包含代码少了。2.共享数据:多个线程共同操作的变量。3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。要求:多个线程必须要共用同一把锁。补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
/*** 例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式*/
public class SyncTest1 {public static void main(String[] args) {Window1 w = new Window1();Thread t1 = new Thread(w);Thread t2 = new Thread(w);Thread t3 = new Thread(w);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}class Window1 implements Runnable{private int ticket = 100;//总票数@Overridepublic void run() {while(true){synchronized (Window1.class){//Window1.class可换成thisif (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);ticket--;} else {break;}}}}
}
方式2:同步方法
synchronized声明在方法中,表示整个方法是同步方法
/*** 例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式*/
public class SyncTest2 {public static void main(String[] args) {Window2 w = new Window2();Thread t1 = new Thread(w);Thread t2 = new Thread(w);Thread t3 = new Thread(w);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}class Window2 implements Runnable{private int ticket = 100;//总票数@Overridepublic void run() {while(true){printTicket();}}public synchronized void printTicket(){if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);ticket--;}}
}
方式3:Lock(锁)(JDK5.0后)
import java.util.concurrent.locks.ReentrantLock;/*** 例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式*/
public class SyncTest3 {public static void main(String[] args) {Window3 w = new Window3();Thread t1 = new Thread(w);Thread t2 = new Thread(w);Thread t3 = new Thread(w);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}class Window3 implements Runnable{private int ticket = 100;//总票数//实例化ReentrantLockprivate ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while(true){try{//调用锁定方法lock()lock.lock();if(ticket > 0){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);ticket--;}else{break;}}finally {//调用解锁方法:unlock()lock.unlock();}}}
}
synchronized与Lock的异同?
相同:二者都可解决线程安全问题
不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
线程的死锁问题
死锁:不同的线程分别占用对方需要的资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。出现死锁后,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
线程的通信
涉及到的三个方法:
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。说明:
1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegalMonitorStateException异常
3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
/*** 线程通信的例子:使用两个线程打印 1-100。线程1, 线程2 交替打印*/
public class CommunicationTest {public static void main(String[] args) {Number t1=new Number();Number t2=new Number();t1.setName("线程1");t2.setName("线程2");t1.start();t2.start();}
}class Number extends Thread{private static int number = 1;@Overridepublic void run() {while(true){synchronized (Number.class) {//唤醒被wait()的进程Number.class.notify();if(number <= 100){try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":" + number);number++;try {//使得调用如下wait()方法的线程进入阻塞状态Number.class.wait();} catch (InterruptedException e) {e.printStackTrace();}}else{break;}}}}
}
五、其他
①AtomicInteger的用法:原子类的类,线程安全的
java.util.concurrent.atomic 的包里有AtomicBoolean, AtomicInteger,AtomicLong,AtomicLongArray
AtomicReference等原子类的类,主要用于在高并发环境下的高效程序处理。
AtomicInteger是在使用非阻塞算法实现并发控制,在一些高并发程序中非常适合,但并不能每一种场景都适合,不同场景要使用使用不同的数值类。
1:创建
AtomicInteger atomicInteger = new AtomicInteger();
2:获取当前值
atomicInteger.get();
3:设置当前值
atomicInteger.set(10);
4:比较并设置
//对比当前值是否和11是否相等,相等返回true否则false,并更新值为30.
Boolean b=atomicInteger.compareAndSet(11,30);
5:getAndAdd()方法与AddAndGet方法
atomicInteger.getAndAdd(10);//获取当前值,并加10
atomicInteger.addAndGet(10);//获取加10后的值,先加10
6:getAndDecrement()和DecrementAndGet()方法
atomicInteger.getAndDecrement();//获取当前值并自减
atomicInteger.decrementAndGet();//先自减再获取减1后的值
7:getAndIncrement()方法和incrementAndGet()
atomicInteger.getAndIncrement();//当前值自增
atomicInteger.incrementAndGet();//先自增再获取当前值
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
private volatile int value;
//unsafe是java提供的获得对对象内存地址访问的类,它的作用就是在更新操作时提供“比较并替换”的作用。
valueOffset是用来记录value本身在内存的地址的,主要是为了在更新操作在内存中找到value的位置,方便比较。
注意:value是用来存储整数的时间变量,这里被声明为volatile,就是为了保证在更新操作时,当前线程可以拿到value最新的值(并发环境下,value可能已经被其他线程更新了)。
②CountDownLatch用法
CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程执行完后再执行。
Java 多线程学习(二)
Java volatile学习
Java CAS学习
Java JUC高并发编程(一)
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
