Java默认有2个线程:main
+ GC
并发:CPU单核,交替执行
并行:CPU多核,多个线程可以同时执行(提高使用效率:线程池)
Runtime.getRuntime().availableProcessors() //当前CPU可用核数
多线程实现方式
继承 Thread 类,重写 run 方法
这样代码的写法简单,符合大家的习惯,但是直接继承Thread类有一个很大的缺点,因为“java类的继承是单一的,extends后面只能指定一个父类”,所有如果当前类继承Thread类之后就不可以继承其他类。如果我们的类已经从一个类继承(如Swing继承自 Panle 类、JFram类等),则无法再继承 Thread 类,这时如果我们又不想建立一个新的类,应该怎么办呢?
class MyThread extends Thread {
private int ticket = 5;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (ticket > 0) {
System.out.println("车票第" + ticket-- + "张");
}
}
}
}
public void Test() {
new MyThread().start();
new MyThread().start();
new MyThread().start();
}
实现 Runnable 接口,重写 run 方法,实现 Runnable 接口的实现类的实例对象作为 Thread 构造函数的 target
如果要实现多继承就得要用implements,Java 提供了接口 java.lang.Runnable来解决上边的问题。
Runnable是可以共享数据的,多个Thread可以同时加载一个Runnable,当各自Thread获得CPU时间片的时候开始运行Runnable,Runnable里面的资源是被共享的,所以使用Runnable更加的灵活。
class MyRunnable implements Runnable {
private int ticket = 5;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (ticket > 0) {
System.out.println("车票第" + ticket-- + "张");
}
}
}
}
public void Test() {
Runnable myRunnable = new MyRunnable();
// 将myRunnable作为Thread target创建新的线程
new Thread(myRunnable).start();
new Thread(myRunnable).start();
}
- 在第二种方法(Runnable)中,ticket输出的顺序并不是54321,这是因为线程执行的时机难以预测,
ticket--
并不是原子操作。 - 在第一种方法中,我们new了3个Thread对象,即三个线程分别执行三个对象中的代码,因此便是三个线程去独立地完成卖票的任务;而在第二种方法中,我们同样也new了3个Thread对象,但只有一个Runnable对象,3个Thread对象共享这个Runnable对象中的代码,因此,便会出现3个线程共同完成卖票任务的结果。如果我们new出3个Runnable对象,作为参数分别传入3个Thread对象中,那么3个线程便会独立执行各自Runnable对象中的代码,即3个线程各自卖5张票。
- 在第二种方法中,由于3个Thread对象共同执行一个Runnable对象中的代码,因此可能会造成线程的不安全,比如可能ticket会输出-1(如果我们System.out....语句前加上线程休眠操作,该情况将很有可能出现),这种情况的出现是由于,一个线程在判断ticket为1>0后,还没有来得及减1,另一个线程已经将ticket减1,变为了0,那么接下来之前的线程再将ticket减1,便得到了-1。这就需要加入同步操作(即互斥锁),确保同一时刻只有一个线程在执行每次for循环中的操作。而在第一种方法中,并不需要加入同步操作,因为每个线程执行自己Thread对象中的代码,不存在多个线程共同执行同一个方法的情况。
通过 Callable 和 FutureTask 创建线程
Runnable是执行工作的独立任务,但是它不返回任何值。如果你希望任务在完成的能返回一个值,那么可以实现Callable接口而不是Runnable接口。在Java SE5中引入的Callable是一种具有类型参数的泛型,它的参数类型表示的是从方法call()
(不是run()
)中返回的值。
FutureTask具有仅执行1次run()方法的特性(即使有多次调用也只执行1次),避免了重复查询的可能。
class MyThread implements Callable<Integer> {
public Integer call() throws Exception {
System.out.println("当前线程名——" + Thread.currentThread().getName());
int i = 0;
for (; i < 5; i++) {
System.out.println("循环变量i的值:" + i);
}
return i;
}
}
public void Test() {
MyThread myThread = new MyThread();
FutureTask<Integer> futureTask = new FutureTask<>(myThread);
new Thread(futureTask, "线程名:有返回值的线程2").start();
try {
System.out.println("子线程的返回值:" + futureTask.get());
} catch (Exception e) {
e.printStackTrace();
}
}
通过线程池创建线程
ExecutorService executorService = Executors.newFixedThreadPool(5);
MyRunnable myRunable = new MyRunnable();
executorService.execute(myRunable);
executorService.shutdown();
-
ExecutorService、Callable、Future三个接口实际上都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征,有了这种特征就不需要再为了得到返回值而大费周折了。
-
有返回值的任务必须实现Callable接口。类似的,无返回值的任务必须实现Runnable接口。
-
执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。
-
注意:get方法是阻塞的,即:线程无返回结果,get方法会一直等待。
-
再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。
-
下面提供了一个完整的有返回结果的多线程测试例子,在JDK1.5下验证过没问题可以直接使用。
实现Runnable接口相比继承Thread类有如下优势:
- 可以避免由于Java的单继承特性而带来的局限;
- 增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的;
- 适合多个相同程序代码的线程区处理同一资源的情况。
实现Runnable接口和实现Callable接口的区别:
- Runnable是自从java1.1就有了,而Callable是1.5之后才加上去的
- Callable规定的方法是call(),Runnable规定的方法是run()
- Callable的任务执行后可返回值,而Runnable的任务是不能返回值(是void)
- call方法可以抛出异常,run方法不可以
- 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
- 加入线程池运行,Runnable使用ExecutorService的execute方法,Callable使用submit方法。
线程的六种状态
- NEW:初始化状态,未调用start方法
- RUNNABLE:运行态
- Ready:等待CPU发时间片
- Running:执行态
- BLOCKED:阻塞态,锁
- WAITING:等待态,拿到锁之后,发现当前执行条件不太满足,需要暂时停止执行,以便让出CPU资源供其他线程执行,wait(),继续执行需要notify()
- TIMED_WAITING:超时等待
- TERMINATED:结束态
Sleep 和 wait 的区别
- Sleep
- 暂停当前线程执行,但不释放锁
- Thread.Sleep()
- 可以在任何场景使用
- 只能等待sleep结束
- wait
- 暂停当前线程执行,并释放锁
- 属于对象
- 不能写在 synchronized 同步块之外
- notify() 唤醒
线程的优先级越高越先执行吗?
Java线程
- 轻量级进程 -> 系统内核线程,即操作系统原生的线程
- 优先级:1-10,win系统只有7级
- 系统会改变优先级,win存在优先级推进器的功能
CAS(Compare And Swap)
- 内存位置,预期值,新值
- 原子操作,CPU硬件指令集提供,硬件保证一个语义看起来需要多次操作才能完成的行为,通过一条处理器指令CAS就能完成
- JDK1.5之后提供了CAS操作 Unsafe类
volatile
-
volatile关键字主要是使变量在多个线程间可见
-
线程的私有堆栈:Java内存模型告诉我们,各个线程会将共享变量从主内存中拷贝到工作内容,然后执行引擎会基于工作内存中的数据进行操作处理。这个时机对普通变量是没有规定的,而针对volatile修饰的变量给java虚拟机特殊的约定,线程对volatile变量的修改会立刻被其他线程所感知,即不会出现数据脏读的现象,从而保证数据的”可见性“。
-
只保证可见性,并不保证原子性
CountDownLatch 减计数器
https://www.cnblogs.com/cxuanBlog/p/14166322.html
CyclicBamer 加计数器
https://www.jianshu.com/p/4ef4bbf01811
Semaphore 信号量
https://www.jianshu.com/p/38630b7dbe73
标签:Runnable,Java,Thread,笔记,Callable,线程,new,ticket,多线程 From: https://www.cnblogs.com/Bota5ky/p/17419049.html