1.什么是进程? - 正在执行的程序
2.什么是线程? - 进程的子单位,一个能够完成独立功能的执行路径
3.为什么需要开启多线程?
a. 当执行某些耗时操作的任务的时候需要开启多线程,防止线程阻塞
b. 能够让两个任务看起来像是在同时执行
c. 提高CPU的使用率,进而提高进程和内存的使用率
4.为什么开启多线程会同时执行? - 因为CPU切换执行的速度太快了,肉眼无法差距
5.开启多线程是不是越多越好,提高了效率还是降低了效率? - 不是,线程越多,效率越慢,但是太少,浪费CPU资源,所以,合理利用CPU
6.并发和并行的区别
-
并发 --> 在同一个时间段下同时执行多个线程,看起来像同时执行
-
并行 --> 在同一个时间刻度下(不能够在分割的时间单位)执行多个线程,本质就上就是同时执行
-
CPU在某一个最小的时间刻度单位下,执行的是一个进程的一个线程的一个不可再分割的原子性语句
-
举例: a++ 是线程安全的吗? 不是
7.同步和异步的区别 – 后期回顾
8.Java虚拟机的启动至少开启了两条线程,主线程和垃圾回收线程
9.一个线程可以理解为进程的子任务
如何来写线程:
任务
任务参数
任务结果
线程的启动方式本质有两种:
1.继承Thread类的方式
2.实现Runnable的方式
-
方式一:继承Thread类
1.自定义类MyThread继承Thread类。
2.MyThread类里面重写run()方法。
3.创建线程对象。
4.启动线程。
注意:
1、启动线程使用的是start()方法而不是run()方法
2、线程能不能多次启动
代码演示使用方式一开启线程 -
方式二:实现Runnable接口
1.自定义类MyRunnable实现Runnable接口
2.重写run()方法
3.创建MyRunnable类的对象
4.创建Thread类的对象,并把步骤3创建的对象作为构造参数传递
启动线程
实现接口方式的好处
可以避免由于Java单继承带来的局限性。
适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。
多线程的实现方式三: 实现Callable方式开启线程
-
继承Thread和实现Runnable的方式的特点:
1.没有返回结果
2.没有异常 -
Callable和Runnable的区别:
1.Runnable无返回值,没有异常抛出的
2.Callable可以在启动线程中获取返回值,以及接受子线程的异常 -
线程间的数据传递:线程通信
A线程中开启了B线程
A --> B 通过构造方法
B --> A 通过Callable方式
匿名内部类的方式开启线程
- 注意: 当继承Thread和实现Runnable方式同时实现,继承Thread优先
public class ThreadDemo04 {
public static void main(String[] args) {
new Thread(); // 匿名线程对象
// 方式一继承Thread方式开启线程
new Thread() {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("A.继承Thread方式:" + i);
}
}
}.start();
// 方式二实现Runnable接口的方式开启线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("B.实现Runnable接口的方式:" + i);
}
}
}).start();
for (int i = 0; i < 100; i++) {
System.out.println("C.主线程:" + i);
}
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("D.实现Runnable接口的方式:" + i);
}
}
}) {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("E.继承Thread方式:" + i);
}
}
}.start();
}
}
Lambda表达式开启线程
什么是Lambda表达式 一种函数式接口的新的写法,本质还是匿名内部类,但是这个父类是函数式接口
-
什么是函数式接口?
– 只有一个抽象方法的接口称为函数式接口
Runnable, FileFilter -
Lambda表达式的语法:
() -> {}
() 小括号里面是参数列表,如果一个函数式接口中的抽象方法没有参数,这里可以不写参数
-> 固定格式
{} 重写函数式接口的抽象方法的方法体
如果方法体中只有一条语句,{} 可以省略不写,如果返回值只有一条语句, return关键字可以省略
public class ThreadDemo05 {
public static void main(String[] args) {
new Demo().method(new ITest() {
@Override
public void show() {
System.out.println("show");
}
});
new Demo().method(()->System.out.println("Lambda表达式的 show"));
new Demo().method((a, b)->System.out.println(a + "|" + b));
new Demo().method((a, b)->a + b);
// com.sxt.threaddemo
File f = new File("src/com/sxt/threaddemo");
/*File[] files = f.listFiles(new FileFilter() {
@Override
public boolean accept(File f) {
return f.isFile() && f.getName().endsWith(".java");
}
});*/
/*File[] files = f.listFiles((file) -> file.isFile() && file.getName().endsWith(".java"));
for (File file : files) {
System.out.println(file);
}*/
ArrayList<String> list = new ArrayList<>();
list.add("张三丰");
list.add("李四");
list.add("赵六");
list.add("王五哈哈哈");
list.forEach((t)->{
System.out.println(t);
});
list.forEach(new Consumer<String>() {
@Override
public void accept(String t) {
System.out.println(t);
}
});
list.removeIf((t)-> t.length() == 2);
System.out.println(list);
/*
* new Consumer<String>() {
@Override
public void accept(String t) {
System.out.println(t);
}
}
Consumer<? super E> action = new Consumer<String>() {
@Override
public void accept(String t) {
System.out.println(t);
}
}
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
*/
new Thread(()->{
// System.out.println("Lambda表达式开启线程");
for (int i = 0; i < 10000; i++) {
System.out.println("Lambda:" + i);
}
}).start();
for (int i = 0; i < 10000; i++) {
System.out.println("main:" + i);
}
}
}
@FunctionalInterface
interface ITest {
void show();
// void test();
}
@FunctionalInterface
interface IDemo {
void show(int a, String b);
// void test();
}
@FunctionalInterface
interface IShow {
int add(int a, int b);
// void test();
}
class Demo {
/*
* ITest test = new ITest() {
@Override
public void show() {
System.out.println("show");
}
};
*/
public void method(ITest test) {
test.show();
}
public void method(IDemo d) {
d.show(10, "sss");
}
public void method(IShow s) {
int add = s.add(100, 200);
System.out.println(add);
}
}
设置和获取线程名称的几种方式
1.通过构造方法
2.通过set/get方法
3.通过静态方法
public class ThreadDemo01 {
public static void main(String[] args) {
/*MyThread t = new MyThread();
t.start();
MyThread t2 = new MyThread();
t2.start();*/
/*MyThread t3 = new MyThread("隔壁老王");
t3.start();*/
/*Thread t3 = new Thread("隔壁老王");
t3.start();*/
/*MyThread t3 = new MyThread();
t3.setName("隔壁老王家");
t3.start();*/
/*Thread.currentThread().setName("主线程");
String name = Thread.currentThread().getName();
System.out.println(name);*/
Thread t4 = new Thread(new MyRunnable(), "隔壁老李");
t4.start();
/*long mainId = Thread.currentThread().getId();
System.out.println(mainId);*/
}
}
class MyThread extends Thread {
/*public MyThread() {
super();
}
public MyThread(String name) {
super(name);
}*/
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName() + ":" + i);
}
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
System.out.println("子线程: " + Thread.currentThread().getId());
}
}
中断线程
public final void stop()
public void interrupt()
面试题: stop和interrupt的区别
stop方法表示结束线程的生命
interrupt表示向线程抛出一个InterruptedException异常
public class ThreadDemo04 {
public static void main(String[] args) {
Thread t = new Thread(new StopThread());
t.start();
System.out.println("主线程: 嘘,开始数,准备好");
try {
Thread.sleep(4000);
// t.stop();
t.interrupt();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class StopThread implements Runnable {
@Override
public void run() {
String startTime = new SimpleDateFormat("开睡时间: yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println(startTime);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("谁打我!!!");
System.out.println("打扰了,我自己来");
System.exit(0);
}
String endTime = new SimpleDateFormat("觉醒时间: yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println(endTime);
}
}
后台线程
public final void setDaemon(boolean on)
一般来说,JVM(JAVA虚拟机)中一般会包括俩种线程
线程分类
a.用户线程
b.后台线程 、守护线程、服务线程
所谓后台线程(daemon)线程指的是:在程序运行的时候在后台提供的一种通用的服务的线程,并且这种线程并不属于程序中不可或缺的部分。
因此,当所有的非后台线程结束的时候,也就是用户线程都结束的时候,程序也就终止了。同时,会杀死进程中的所有的后台线程。
反过来说,只要有任何非后台线程还在运行,程序就不会结束。比如执行main()的就是一个非后台线程。
基于这个特点,当虚拟机中的用户线程全部退出运行时,守护线程没有服务的对象后,JVM也就退出了。
线程加入
public final void join()
public class ThreadDemo06 {
public static void main(String[] args) {
JoinThread t1 = new JoinThread();
JoinThread t2 = new JoinThread();
JoinThread t3 = new JoinThread();
t1.setName("刘备");
t2.setName("关羽");
t3.setName("张飞");
t1.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
t3.start();
}
}
class JoinThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
线程礼让
public static void yield()
- 让正在执行的线程让出CPU的执行权一小会
- t1和t2互相争夺CPU的执行权,t1抢到了CPU的执行权,让出CPU的执行权,重新回到线程队列中继续抢夺CPU的执行权
public class ThreadDemo07 {
public static void main(String[] args) {
YieldThread t1 = new YieldThread();
YieldThread t2 = new YieldThread();
t1.setName("孔融");
t2.setName("孔融的哥哥");
t1.start();
t2.start();
}
}
class YieldThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
Thread.yield();
}
}
}
死锁:指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象。
代码演示死锁现象。
public class DeadThradDemo {
public static void main(String[] args) {
DieLock dl1 = new DieLock(true);
DieLock dl2 = new DieLock(false);
dl1.start();
dl2.start();
}
}
class DieLock extends Thread {
private boolean flag;
public DieLock() {
}
public DieLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
// dl1进来
synchronized (TestLock.LOCKA) {
System.out.println("if 语句中 LockA锁"); // 就在输出完这句话之后被dl2抢到了资源
synchronized (TestLock.LOCKB) {
System.out.println("if 语句中 LockB锁");
}
}
} else {
// dl走else
synchronized (TestLock.LOCKB) {
System.out.println("else 语句中 lockB锁");
synchronized (TestLock.LOCKA) {
System.out.println("else 语句中 lockA锁");
}
}
}
}
}
class TestLock {
public static final Object LOCKA = new Object();
public static final Object LOCKB = new Object();
}
需求:深圳罗湖火车站目前正在出售车票,共有100张票,而它有3个售票窗口售票,
-
请设计一个程序模拟该火车站售票。
-
要求:使用两种方式实现
继承Thread类
实现Runnable接口
继承Thread和实现Runnable的方式的区别
1.继承Thread可以直接使用 Thread里面的方式
2.实现接口方式的好处
a.可以避免由于Java单继承带来的局限性。
b.适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。
出现的问题
1.卖出了同票
窗口A正在出售第97张票
窗口B正在出售第97张票
窗口C正在出售第97张票
2.卖出了负票
窗口A正在出售第1张票
窗口C正在出售第0张票
窗口B正在出售第-1张票
问题产生的原因: CPU在某一个最小的时间刻度单位下,执行的是一个进程的一个线程的一个不可再分割的原子性语句
解决办法:
1.同步代码块
2.同步方法
3.同步锁
可能出现线程安全的问题的情况:
1.存在多线程环境
2.多个线程共享同一份数据
3.多个线程操作同一份数据并且共享数据做了修改
4.存在多条语句操作共享数据
在多线程环境下,存在多条语句操作共享数据,并且对数据做了修改的操作,那么必定会出现线程安全问题
解决办法: 将多条操作共享数据的语句 包裹起来,同步锁
public class ThreadSynchronizationDemo {
public static void main(String[] args) {
/* SellTicketThread t1 = new SellTicketThread();
SellTicketThread t2 = new SellTicketThread();
SellTicketThread t3 = new SellTicketThread();*/
SellTicketThread st = new SellTicketThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
Thread t3 = new Thread(st);
t1.setName("窗口A");
t2.setName("窗口B");
t3.setName("窗口C");
t1.start();
t2.start();
t3.start();
}
}
/*
* 解决办法方式一:同步代码块
格式:
synchronized(对象){需要同步的代码;}
同步的好处
解决了多线程的安全问题。
同步的弊端
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,降低程序的运行效率。如果出现了同步嵌套,就容易产生死锁问题
注意: 这里的对象是锁对象,可以是任意对象,但是必须多个线程共享同一个对象
这种方式加锁的时间是进入代码之后,释放锁的时间是代码块执行结束的时候
*/
class SellTicketThread implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (true) {
synchronized (MyLock.LOCK) {
if (tickets > 0) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
}
}
enum MyLock {
LOCK
}
/*
* 解决办法方式二:同步方法
格式:
public synchronized 返回值 方法名(参数列表) {
//需要同步的代码块
}
如果锁对象是this,就可以考虑使用同步方法。
如果方式静态方法, 当前类对应的字节码文件对象作锁 Class c = SellTicketThread.class
*/
/*class SellTicketThread implements Runnable {
private static int tickets = 100;
@Override
public void run() {
while (true) {
sellTicket();
}
}
public static synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
}*/
/*class SellTicketThread extends Thread {
private static int tickets = 100;
@Override
public void run() {
while (true) {
sellTicket();
}
}
public static synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
}*/
/*class SellTicketThread implements Runnable {
private static int tickets = 100;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
lock.unlock();
}
}
}
*/
线程池和线程组的区别
线程组为了方便管理线程,可以将线程分组
ThreadGroup
主线程的组名叫做 main
public class ThreadGroupDemo {
public static void main(String[] args) {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
System.out.println(tg.getName());
ThreadGroupRunnable tr = new ThreadGroupRunnable();
ThreadGroup sgyy = new ThreadGroup("三国演义组");
// 第一组 三国演义组
Thread t1 = new Thread(sgyy, tr, "诸葛亮");
Thread t2 = new Thread(sgyy, tr, "司马懿");
Thread t3 = new Thread(sgyy, tr, "周瑜");
ThreadGroup shz = new ThreadGroup("水浒传");
// 第一组 三国演义组
Thread t4 = new Thread(shz, tr, "李逵");
Thread t5 = new Thread(shz, tr, "宋江");
Thread t6 = new Thread(shz, tr, "卢俊义");
/*t1.start();
t2.start();
t3.start();*/
List<Thread> threadList = new ArrayList<Thread>();
threadList.add(t1);
threadList.add(t2);
threadList.add(t3);
// 批量设置为后台线程
sgyy.setDaemon(true);
sgyy.stop();
for (Thread thread : threadList) {
thread.start();
}
System.out.println(t5.getThreadGroup().getName());
}
}
class ThreadGroupRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
Executors工厂类来产生线程池。
构造方法
public static ExecutorService newCachedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
public class ThreadPoolDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService pool = Executors.newFixedThreadPool(3);
pool.submit(new MyRunnable());
Future<Integer> future = pool.submit(new MyCallable(1, 100));
pool.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
});
Integer i = future.get();
System.out.println("返回的结果: " + i);
pool.shutdown();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
class MyCallable implements Callable<Integer> {
int m;
int n;
public MyCallable(int m, int n) {
super();
this.m = m;
this.n = n;
}
public MyCallable() {
super();
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = m; i <= n; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
sum += i;
}
return sum;
}
}
volatile关键字
原因分析:
现在有两个线程,一个是main线程,另一个是RunThread。
它们都试图修改 第三行的 isRunning变量。
按照JVM内存模型,main线程将isRunning读取到本地线程内存空间,修改后,再刷新回主内存。
线程会一直在私有堆栈中读取isRunning变量。
因此,RunThread线程无法读到main线程改变的isRunning变量
从而出现了死循环,导致RunThread无法终止。
解决方法,在第三行代码处用 volatile 关键字修饰即可。
这里,它强制线程从主内存中取 volatile修饰的变量。
总结: volatile关键字的作用是:使变量在多个线程间可见(可见性)
Timer定时器对象和TimerTask 定时器任务
public class TimerTest {
public static void main(String[] args) {
Timer t = new Timer();
TimerTask task = new MyTask(t);
Calendar c = Calendar.getInstance();
c.set(2019, 7, 24, 15, 24, 30);
long time = c.getTimeInMillis();
Date d = new Date(time);
// 规定制定的时间启动线程
// t.schedule(task, d);
// t.schedule(task, 3000L);
t.schedule(task, 3000, 1000);
}
}
class MyTask extends TimerTask {
private Timer t;
public MyTask(Timer t) {
super();
this.t = t;
}
public MyTask() {
super();
}
@Override
public void run() {
System.out.println("Boom!!!");
// t.cancel();
}
}