什么是线程
在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。是程序使用CPU的基本单位。所以,进程是拥有资源的基本单位, 线程是CPU调度的基本单位。
多线程有什么意义呢?
多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。
那么怎么理解这个问题呢?
我们程序在运行的使用,都是在抢CPU的时间片(执行权),如果是多线程的程序,那么在抢到
CPU的执行权的概率应该比较单线程程序抢到的概率要大.那么也就是说,CPU在多线程程序
中执行的时间要比单线程多,所以就提高了程序的使用率.但是即使是多线程程序,那么他们
中的哪个线程能抢占到CPU的资源呢,这个是不确定的,所以多线程具有随机性.
两个词汇的区别:并行和并发。
前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。
后者是物理上同时发生,指在某一个时间点同时运行多个程序。
由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。
而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。
但是Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。
但是呢?Java可以去调用C/C++写好的程序来实现多线程程序。
由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,
然后提供一些类供我们使用。我们就可以实现多线程程序了。
几个小问题:
启动线程使用的是哪个方法-------start()方法
线程能不能多次启动-------执行多次会报错
run()和start()方法的区别
我们启动线程使用不是run方法,而应该是start方法.使该线程开始执行;
Java 虚拟机调用该线程的 run 方法。
为什么要重写run方法?
这个类是一个线程类,那么在这个类中我们可不可以写一些其他的方法呢?
我们可以在写其他的方法,那么其他方法中封装的代码都是需要被我们线程执行的吗? 不一定
那么也就是run方法中封装应该是必须被线程执行的代码.
run方法中的代码的书写原则: 一般是比较耗时的代码
多线程程序实现的方式1
(1):创建一个继承Thread的类并重写run()方法
(2):创建该类对象
(3):用对象开启线程
案例演示: 多线程程序实现的方式1
//第一步操作:创建一个继承Thread的类并重写run()方法
public class MyThread extends Thread{
@Override
public void run() {
//这个run方法就是需要线程来执行的代码,一般耗时的操作,我们就会写在run方法里面,让线程去执行
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
------------------------------------------------------------------------------
public class MyTest3 {
public static void main(String[] args) {
//第二步操作:在测试类中创建第一步创建的类的对象
MyThread th = new MyThread();
//用创建好的对象调用start()方法开启线程
th.start();
}
}
//第一步操作:创建一个实现Runnable接口的类并重写run()方法
public class MyRunnable implements Runnable{
@Override
public void run() {
//需要线程执行
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
------------------------------------------------------------------------------
public class MyTest {
public static void main(String[] args) {
//第二步操作:在测试类中创建第一步创建的类的对象
MyRunnable myRunnable = new MyRunnable();
//Thread(Runnable target)
//第三步操作:分配新的 Thread 对象。
Thread th = new Thread(myRunnable);
//第四步操作:用对象开启线程
th.start();
new MyThread().start();
}
}
多线程程序实现的方式3
(1):创建一个实现Callable接口的类(有泛型接口)并重写call()方法
(2):创建该类对象
(3):创建一个FutureTask类将Callable接口的子类对象作为参数传进去
(4):分配新的Thread对象
(5):用对象开启线程
实现 Callable 接口。 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。
执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类
案例演示:多线程程序实现的方式3
//第一步操作:创建一个实现Callable接口的类(有泛型接口)并重写call()方法
public class MyCallable implements Callable {
//call方法就是线程要执行的方法
@Override
public Integer call() throws Exception {
System.out.println("线程执行了");
int sum=0;
for (int i = 1; i <= 100; i++) {
sum+=i;
}
return sum;
}
}
-------------------------------------------------------------------------------
public class MyTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//第二步操作:在测试类中创建第一步创建的类的对象
MyCallable myCallable = new MyCallable();
//第三步操作:创建一个FutureTask类将Callable接口的子类对象作为参数传进去
FutureTask task = new FutureTask<>(myCallable);
//第四步操作:分配新的Thread对象
Thread thread = new Thread(task);
//第五步操作:用对象开启线程
thread.start();
//线程执行完之后,可以获取结果
Integer integer = task.get();
System.out.println(integer);
}
}
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
-------------------------------------------------------------------------------
public class MyTest {
public static void main(String[] args) {
//我如何获取主线程名称
Thread thread = Thread.currentThread();//获取当前线程对象
thread.setName("这是主线程");
String name = thread.getName();
System.out.println(name);
System.out.println("main方法执行了");
MyThread myThread = new MyThread();
MyThread myThread2 = new MyThread();
//设置线程的名称
myThread.setName("刘亦菲");
myThread2.setName("范冰冰");
myThread.start();
myThread2.start();
}
}
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
-------------------------------------------------------------------------------
public class MyTest {
public static void main(String[] args) {
//Java 使用线程的调度模型,是抢占式调度
//如果说多个线程的优先级一样,线程的执行,就是随机抢占
//我们如何设置线程的优先级
MyThread th1 = new MyThread();
MyThread th2 = new MyThread();
//设置线程的优先级 1----10
th1.setPriority(10);
th2.setPriority(Thread.MAX_PRIORITY);
//获取线程的优先级
int priority = th1.getPriority(); //默认优先级是5
int priority1 = th2.getPriority();
System.out.println(priority);
System.out.println(priority1);
th1.setName("李冰冰");
th2.setName("范冰冰");
th1.start();
th2.start();
}
}
线程休眠:public static void sleep(long millis) :线程休眠
加入线程:public final void join():等待该线程执行完毕了以后,其他线程才能再次执行
礼让线程:public static void yield(): 暂停当前正在执行的线程对象,并执行其他线程。 (效果不明显,暂停的时候其他线程可能抢占不到CPU的执行权)
守护线程:public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。(该方法必须在启动线程前调用)
中断线程:
public final void stop():停止线程的运行
public void interrupt():中断线程(这个翻译不太好),查看API可得当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞
Java用户线程和守护线程
1.用户线程和守护线程的区别
用户线程和守护线程都是线程,区别是Java虚拟机在所有用户线程dead后,程序就会结束。而不管是否还有守护线程还在运行,若守护线程还在运行,则会马上结束。很好理解,守护线程是用来辅助用户线程的,如公司的保安和员工,各司其职,当员工都离开后,保安自然下班了。
2.用户线程和守护线程的适用场景
由两者的区别及dead时间点可知,守护线程不适合用于输入输出或计算等操作,因为用户线程执行完毕,程序就dead了,适用于辅助用户线程的场景,如JVM的垃圾回收,内存管理都是守护线程,还有就是在做数据库应用的时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监听连接个数、超时时间、状态等。
3.创建守护线程
调用线程对象的方法setDaemon(true),设置线程为守护线程。
1)thread.setDaemon(true)必须在thread.start()之前设置。
2)在Daemon线程中产生的新线程也是Daemon的。
3)不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。
因为Daemon Thread还没来得及进行操作,虚拟机可能已经退出了。
4.Java守护线程和Linux守护进程
两者不是一个概念。Linux守护进程是后台服务进程,没有控制台。
在Windows中,你可以运行javaw来达到释放控制台的目的,在Unix下你加&在命令的最后就行了。所以守护进程并非一定需要的。
案例演示
需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
通过继承Thread类实现
分析:
a: 三个窗口其实就是3个线程
b: 定义票的数量100张
c: 创建线程对象,启动线程. 每卖一张这个票数应该-1
public class MyThread extends Thread{
static int tickets = 100;
@Override
public void run() {
while (tickets>0){
if(tickets>=1){
String name = this.getName();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(tickets>=1) {
System.out.println(name + "正在出售" + (tickets--) + "张票");
}
}
}
}
}
---------------------------------------------------------------------------------------------------------------------------------------
public class MyTest {
public static void main(String[] args) {
MyThread th1 = new MyThread();
MyThread th2 = new MyThread();
MyThread th3 = new MyThread();
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
首先想为什么出现问题?(也是我们判断是否有问题的标准)
是否是多线程环境
是否有共享数据
是否有多条语句操作共享数据
如何解决多线程安全问题呢?
基本思想:让程序没有安全问题的环境。
怎么实现呢?
把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。
在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,
因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识。
java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。
线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。
获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,
当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,
直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,
但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,
类锁是用于类的静态方法或者一个类的class对象上的。
我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,
所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,
它只是用来帮助我们理解锁定实例方法和静态方法的区别的.
public class MyRun implements Runnable {
int tickets = 100;
static Object obj = new Object();
@Override
public void run() {
String name = Thread.currentThread().getName();
while (tickets > 0) {
synchronized (obj) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(tickets>=1){
System.out.println(name + "正在出售第" + (tickets--) + "票");
}
}
}
}
}
---------------------------------------------------------------------------------------
public class SellTickets {
public static void main(String[] args) {
MyRun run = new MyRun();
Thread th1 = new Thread(run);
Thread th2 = new Thread(run);
Thread th3 = new Thread(run);
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
多执行几次,肯定会出现死锁情况
public class MyThread extends Thread{
static Object obj1=new Object();
static Object obj2=new Object();
boolean flag;
public MyThread(boolean flag) {
this.flag=flag;
}
@Override
public void run() {
String s = Thread.currentThread().getName();
if(flag){
synchronized (obj1) {
System.out.println(s + "执行了1");
synchronized (obj2) {
System.out.println(s + "执行了2");
}
}
}else {
synchronized (obj2) {
System.out.println(s+"执行了1");
synchronized (obj1) {
System.out.println(s + "执行了2");
}
}
}
}
}
------------------------------------------------------------------------------
public class Test {
public static void main(String[] args) {
MyThread th1 = new MyThread(true);
th1.setName("对象1");
MyThread th2 = new MyThread(false);
th2.setName("对象2");
th1.start();
th2.start();
}
}