线程
多线程
线程和进程:
线程是依赖于进程存在的。
进程概述:
通过任务管理器可以看到进程的存在,进程就是正在运行的程序,是系统进行资源分配和调用的独立单位,每一个进程都有自己的内存空间和系统资源。
多进程的意义:
单进程计算机只能做一件事情,常见的操作系统都是多进程操作系统。比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。
对于单核计算机来讲,两个进程不是同时运行的,因为CPU在某个时间点时只能做一件事,计算机是在两个进程之间来回切换,且切换速度极快。所以,人感觉到的两个进程是同步进行的,其实并不是同时执行的。多进程的作用不是提高执行速度,而是提高CPU的使用率。
线程概述及多线程的意义及并行和并发的区别
什么是线程:
在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。是程序使用CPU的基本单位,进程是拥有资源的的基本单位,线程是CPU调度的基本单位。
多线程的意义:
多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。
程序在运行的使用,都是在抢CPU的时间片(执行权),如果是多线程的程序,那么在抢到CPU的执行权的概率应该比较单线程程序的使用率。但是即使是多线程程序,在抢占CPU资源的时候也是不确定的,所以多线程具有随机性。
注意:并发和并行
并行:逻辑上同时发生,指在某一个时间内同时运行多个程序。
并发:物理上同时发生,指在某一个时间点同时运行多个程序。
并发和并行
并发:
指应用能够交替执行不同的任务,有点类似多线程的原理,多线程并非是如果你开两个线程同时执行多个任务。
并行:
指应用能够同时执行不同的任务。
Java程序运行原理和JVM的启动
java程序运行原理:
java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个“主线程”,然后主线程去调用某个类的main方法。所以main方法运行在主线程中。
JVM启动是多线程的:
JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
多线程程序实现方法
如何实现多线程:
由于线程是依赖进程而存在的,进程是由系统创建的,所以应该去调用系统功能创建一个进程。但是java是不能直接调用系统功能的。Java可以去调用C/C++写好的程序来实现多线程程序。由C/C++去调用系统功能创建进程,然后Java去调用这样的东西,然后提供一些类供用户使用。用户就可以实现多线程程序了。
多线程的实现方法一:
继承Thread类
案例:
package Demo3;
public class Test {
public static void main(String[] args) {
System.out.println("主线程执行了");
MyRead myRead = new MyRead();
//setname()给线程定义名字;子线程默认名字是Thread-0
myRead.setName("子线程1");
myRead.start();
}
}
class MyRead extends Thread{
@Override
public void run() {
//getname()方法,获取线程的名字
System.out.println(this.getName()+"执行了");
}
}
结果:
主线程执行了
子线程1执行了
Process finished with exit code 0
多线程的实现方法二:
实现Runnable接口,这种方法扩展性强,实现一个接口后,还可以继承其他类。
避免了由于Java继承带来的局限性。
案例:
package Demo3;
public class Test2 {
public static void main(String[] args) {
//创建任务
//类的对象是在创建 Thread 时作为一个参数来传递并启动。
MyRunnable runnable = new MyRunnable();
//把任务传递给线程
//在创建新的线程的时候,可以在参数后面定义线程的名称。
Thread th1 = new Thread(runnable,"线程一");
Thread th2 = new Thread(runnable,"线程二");
th1.start();
th2.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
//因为实现Runnable接口,并没有getname()方法,所以需要Thread.currentThread().getName()来获取线程的名字。
System.out.println(Thread.currentThread().getName()+" 执行了");
}
}
结果:
线程一 执行了
线程二 执行了
Process finished with exit code 0
多线程的实现方法三:
实现 Callable 接口。 相较于实现 Runnable 接口的方式,实现 Callable 接口方法可以有返回值,并且可以抛出异常。
执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类
实现步骤
1. 创建一个类实现Callable 接口
2. 创建一个FutureTask类将Callable接口的子类对象作为参数传进去
3. 创建Thread类, 将FutureTask对象作为参数传进去
4. 开启线程
案例:
package Demo3;
import java.util.concurrent.FutureTask;
public class Test1 {
public static void main(String[] args) {
ThreadDemo demo = new ThreadDemo();
FutureTask<String> task = new FutureTask<>(demo);
Thread th = new Thread(task);
th.start();
}
}
//对象类代码块
package Demo3;
import java.util.concurrent.Callable;
public class ThreadDemo implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("子线程执行了");
return null;
}
}
结果:
子线程执行了
Process finished with exit code 0
案例:实现 Callable 接口和实现Runnable接口的区别
package Demo3;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadDemo demo = new ThreadDemo();
FutureTask<String> task = new FutureTask<>(demo);
Thread th = new Thread(task);
th.start();
ThreadDemo demo1 = new ThreadDemo("abcd");
FutureTask<String> task1 = new FutureTask<>(demo1);
Thread th1 = new Thread(task1);
th1.start();
String s = task1.get();
System.out.println(s);
}
}
//实现类代码块
package Demo3;
import java.util.concurrent.Callable;
public class ThreadDemo implements Callable<String> {
private String s;
public ThreadDemo() {
}
public ThreadDemo(String s) {
this.s=s;
}
@Override
public String call() throws Exception {
String s="异步执行结果";
System.out.println("子线程执行了");
return s;
}
}
结果:
子线程执行了
子线程执行了
异步执行结果
Process finished with exit code 0
实现 Callable 接口和实现Runnable接口的区别:
//Runnable 重写run方法,没有返回值,无法获取异步执行完之后的结果 run不允许抛出异常
//Callable<Integer> 重写call方法,有返回值,可以获取异步执行完之后的结果。 call方法可以抛出异常。
获取和设置线程对象名称
Thread类的基本获取和设置方法
public final String getName()//获取线程名称
public final void setName(String name)//设置线程名称
public static Thread currentThread()//获取当前执行的线程
案例:
package Demo3;
public class Test2 {
public static void main(String[] args) {
//Thread.currentThread()获取当前正在运行的线程对象
System.out.println(Thread.currentThread().getName());
//在创建线程的时候给线程命名
Mythread9 thread = new Mythread9("线程一");
thread.start();
//getName()获取此线程的名称
System.out.println(thread.getName());
Mythread9 thread1 = new Mythread9();
//setName()给线程命名
thread1.setName("线程二");
thread1.start();
System.out.println(thread1.getName());
}
}
class Mythread9 extends Thread{
public Mythread9(String name) {
super(name);
}
public Mythread9() {
}
@Override
public void run() {
System.out.println(this.getName()+"执行了");
}
}
结果:
main
线程一
线程二
线程一执行了
线程二执行了
Process finished with exit code 0
线程调度及获取和设置线程优先级
线程的执行
假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,
线程只有得到 CPU时间片,也就是使用权,才可以执行指令。
线程有两种调度模型:
分时调度模型 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,
优先级高的线程获取的 CPU 时间片相对多一些。
Java使用的是抢占式调度模型。
如何设置和获取线程优先级
public final int getPriority() //获取线程的优先级
public final void setPriority(int newPriority)//设置线程的优先级
注意:
有的时候我们给线程设置了指定的优先级,但是该线程并不是按照优先级高的线程执行,因为线程的优先级的大小仅仅表示这个线程被CPU执行的概率增大了.但是我们都知道多线程具有随机性,所以有的时候一两次的运行说明不了问题。
案例:
package Demo3;
public class Test3 {
public static void main(String[] args) {
MyThread thread1 = new MyThread("线程一");
MyThread thread2 = new MyThread("线程二");
thread1.start();
thread2.start();
//获取线程当前优先级,线程优先级是整数类型。
int j = thread1.getPriority();
//查看当前优先级值是多少
System.out.println("当前优先级"+j);
thread2.getPriority();
//设置优先级为最大优先级
thread1.setPriority(Thread.MAX_PRIORITY);
//查看线程优先级最大值是多少
int i = thread1.getPriority();
System.out.println("最大优先级"+i);
//获取查看线程最小优先级是多少
thread2.setPriority(Thread.MIN_PRIORITY);
System.out.println("最小优先级"+thread2.getPriority());
}
}
class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
public MyThread() {
}
@Override
public void run() {
System.out.println(this.getName()+"执行了");
}
}
结果:
当前优先级5
最大优先级10
最小优先级1
线程二执行了
线程一执行了
Process finished with exit code 0
线程控制:休眠线程
线程休眠: public static void sleep(long millis)
参数类型是毫秒值。
案例:
package Demo3;
public class Test4 {
public static void main(String[] args) {
ThreadDemo thread1 = new ThreadDemo("线程一");
ThreadDemo thread2 = new ThreadDemo("线程二");
//线程一启动
thread1.start();
try {
//线程一休眠1000毫秒后执行
thread1.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
}
class ThreadDemo extends Thread{
public ThreadDemo(String name) {
super(name);
}
public ThreadDemo() {
}
@Override
public void run() {
try {
//子线程在休眠1000毫秒后执行
this.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 4; i++) {
System.out.print(this.getName()+i);
}
}
}
结果:
线程一0线程一1线程一2线程一3线程二0线程二1线程二2线程二3
Process finished with exit code 0
分析:
线程休眠程序写入run()方法内时,所有子线程都会在启用时休眠指定毫秒的时间。
指定线程执行线程休眠时,只有此线程会休眠,其他线程正常执行。
线程控制:加入线程
加入线程: public final void join()
意思就是: 等待该线程执行完毕了以后,其他线程才能再次执行
注意事项: 在线程启动之后,在调用方法
案例:
package Demo3;
public class Test5 {
public static void main(String[] args) throws InterruptedException {
ThreadDemo2 thread1 = new ThreadDemo2();
ThreadDemo2 thread2 = new ThreadDemo2();
ThreadDemo2 thread3 = new ThreadDemo2();
thread1.setName("线程一");
thread2.setName("线程二");
thread3.setName("线程三");
//join() 可以让多个线程从并发执行,变成串行。
thread1.start();
thread2.start();
thread3.start();
//三个线程启动,
//线程一执行完才能执行其他线程
thread1.join();
//线程二执行完才能执行其他线程
thread2.join();
}
}
class ThreadDemo2 extends Thread{
@Override
public void run() {
for (int i = 0; i <4; i++) {
System.out.println(this.getName()+i);
}
}
}
结果:
线程一0
线程一1
线程一2
线程一3
线程二0
线程二1
线程二2
线程二3
线程三0
线程三1
线程三2
线程三3
Process finished with exit code 0
线程控制:礼让线程
礼让线程:
public static void yield(): 暂停当前正在执行的线程对象,并执行其他线程。
案例:
package Demo3;
public class Test5 {
public static void main(String[] args) throws InterruptedException {
ThreadDemo2 thread1 = new ThreadDemo2();
ThreadDemo2 thread2 = new ThreadDemo2();
ThreadDemo2 thread3 = new ThreadDemo2();
thread1.setName("线程一");
thread2.setName("线程二");
thread3.setName("线程三");
thread1.start();
thread2.start();
thread3.start();
//三个线程启动,
//暂停当前运行的线程,
//然后重新抢占时间片,此时抢占时间片的线程包括所有线程,不会因为被暂停失去抢占权
Thread.yield();
}
}
class ThreadDemo2 extends Thread{
@Override
public void run() {
for (int i = 0; i <4; i++) {
System.out.println(this.getName()+i);
}
}
}
结果:
线程一0
线程三0
线程三1
线程三2
线程三3
线程二0
线程二1
线程二2
线程二3
线程一1
线程一2
线程一3
Process finished with exit code 0
分析:
这个礼让是要暂停当前正在执行的线程,这个暂停的时间是相当短的,如果在这个线程暂停完毕以后,其他的线程还没有抢占到CPU的执行权,那么这个时候这个线程应该再次和其他线程抢占CPU的执行权.
线程控制:中断线程
中断线程
public final void stop(): 停止线程的运行
public void interrupt(): 中断线程(这个翻译不太好),查看API可得当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞
案例:
package Demo3;
public class Test6 {
public static void main(String[] args) throws InterruptedException {
MyBlog blog1 = new MyBlog("线程一");
MyBlog blog2 = new MyBlog("线程二");
blog1.start();
blog2.start();
//清除线程的阻塞
blog1.interrupt();
blog2.interrupt();
}
}
class MyBlog extends Thread{
public MyBlog(String name ) {
super(name);
}
public MyBlog() {
}
@Override
public void run() {
//线程休眠,使线程处于了一种阻塞的状态。
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 10; i++) {
System.out.println(this.getName()+i);
}
}
}
结果:
//中断异常:睡眠异常
//休眠被清除 异常显示
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at Demo3.MyBlog.run(Test6.java:24)
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at Demo3.MyBlog.run(Test6.java:24)
线程一0
线程一1
线程一2
线程一3
线程一4
线程一5
线程一6
线程一7
线程一8
线程一9
线程二0
线程二1
线程二2
线程二3
线程二4
线程二5
线程二6
线程二7
线程二8
线程二9
Process finished with exit code 0
线程控制:守护线程
守护线程: public final void setDaemon(boolean on):
将该线程标记为守护线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 守护线程就立马死掉
该方法必须在启动线程前调用。
创建守护线程
调用线程对象的方法setDaemon(true),设置线程为守护线程。
thread.setDaemon(true)必须在thread.start()之前设置。
在Daemon线程中产生的新线程也是Daemon的。
不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为Daemon Thread还没来得及进行操作,虚拟机可能已经退出了。
用户线程和守护线程的区别
用户线程和守护线程都是线程,区别是Java虚拟机在所有用户线程dead后,程序就会结束。而不管是否还有守护线程还在运行,若守护线程还在运行,则会马上结束。很好理解,守护线程是用来辅助用户线程的,如公司的保安和员工,各司其职,当员工都离开后,保安自然下班了。
用户线程和守护线程的适用场景
由两者的区别及dead时间点可知,守护线程不适合用于输入输出或计算等操作,因为用户线程执行完毕,程序就dead了,适用于辅助用户线程的场景,如JVM的垃圾回收,内存管理都是守护线程,还有就是在做数据库应用的时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监听连接个数、超时时间、状态等。
案例:
package Demo3;
public class Test7 {
public static void main(String[] args) {
MyBlog2 m1 = new MyBlog2("线程一");
MyBlog2 m2 = new MyBlog2("线程二");
//将线程一设置为守护线程。
m1.setDaemon(true);
m1.start();
m2.start();
}
}
class MyBlog2 extends Thread {
public MyBlog2(String name) {
super(name);
}
public MyBlog2() {
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.getName() + i);
}
}
}
结果:
//用户线程运行完后,守护线程在运行一段时间后,伴随着JVM的结束而结束。
线程二0
线程二1
线程二2
线程二3
线程二4
线程二5
线程二6
线程二7
线程二8
线程二9
线程一0
线程一1
线程一2
线程一3
线程一4
线程一5
线程一6
线程一7
Process finished with exit code 0
线程安全问题的产生原因分析
判断一个多线程应用程序是否有问题的标准:
是否是多线程环境
是否存在共享数据
是否存在多条语句同时操作共享数据
那么我们只要将这个标准打乱,那么我们就可以解决这个问题.
而判断标准中a , b是不能打乱的,因此我们只能对c做处理, 如果我们把操作共享数据的多条语句看做成一个整体,当一个线程执行这个整体的时候,其他的线程处于等待状态,也就说当一个线程执行这个整体的时候,其他线程不能进行执行。
需要使用同步代码块:
格式:
synchronized(对象){
//不能在括号了直接new 对象 new 了 就没效果要被同步的代码 ;
}
这个同步代码块保证数据的安全性的一个主要因素就是这个对象
注意这个对象 要定义为静态成员变量 才能被所有线程共享
需要这个对象被所有的线程对象所共享
这个对象其实就是一把锁.
这个对象习惯叫做监视器
同步代码块的方式解决线程安全问题及解释以及同步的特点及好处和弊端
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能
同步的好处:
同步的出现解决了多线程的安全问题。
同步的弊端:
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,
因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识。
java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。
线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。
获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,
当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,
直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,
但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,
类锁是用于类的静态方法或者一个类的class对象上的。
我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,
所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,
它只是用来帮助我们理解锁定实例方法和静态方法的区别的.
电影院卖票案例:
需求:
电影院三个窗口同时卖100张票,注意:每张票不能被卖出两次,不能出现不存在的票
代码:
package Demo3;
public class Test9 {
public static void main(String[] args) {
sellPiao sellPiao = new sellPiao();
Thread win1 = new Thread(sellPiao);
Thread win2 = new Thread(sellPiao);
Thread win3 = new Thread(sellPiao);
win1.setName("张三");
win2.setName("李四");
win3.setName("王五");
win1.start();
win2.start();
win3.start();
}
}
class sellPiao implements Runnable{
//锁对象
static Object o = new Object();
static int n = 100;
@Override
public void run() {
while (true){
//加锁
synchronized (o){
try {
//模拟真实情况,网络延时
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (n>0){
System.out.println(Thread.currentThread().getName()+"卖出第 "+n+"张票");
n--;
}
}//释放锁
}
}
}
结果:
张三卖出第 100张票
张三卖出第 99张票
王五卖出第 98张票
王五卖出第 97张票
王五卖出第 96张票
李四卖出第 95张票
李四卖出第 94张票
李四卖出第 93张票
李四卖出第 92张票
......
李四卖出第 2张票
李四卖出第 1张票
同步方法和静态同步方法
package Demo3;
public class Test10 {
public static void main(String[] args) {
SellPiao sellPiao = new SellPiao();
Thread win1 = new Thread(sellPiao);
Thread win2 = new Thread(sellPiao);
Thread win3= new Thread(sellPiao);
win1.setName("张三");
win2.setName("李四");
win3.setName("王五");
win1.start();
win2.start();
win3.start();
sellPiao.maipiao();
}
}
class SellPiao implements Runnable{
static Object o = new Object();
static int n=100;
@Override
public void run() {
System.out.println("run方法执行了");
while (true){
synchronized (o){
if (n>0){
System.out.println(Thread.currentThread().getName()+"卖出第 "+n+"张票");
n--;
}
}
}
}
//同步方法:
//方法上加上关键字synchronized 同步方法
//同步方法的默认锁对象是this
public synchronized void maipiao(){
System.out.println("同步方法执行了");
while (true){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (n > 0) {
System.out.println(Thread.currentThread().getName()+"卖出第 "+n+"张票");
n--;
}
}
}
//静态同步方法:用的锁对象是当前类的字节码文件对象
public static synchronized void jingtaimaipiao(){
System.out.println("静态同步方法执行了");
while (true){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (n > 0) {
System.out.println(Thread.currentThread().getName()+"卖出第 "+n+"张票");
n--;
}
}
}
}