什么是多线程
介绍多线程之前要介绍线程,介绍线程则离不开进程。
什么是进程?线程?
进程可以看做一个程序。
进程与线程的区别
多线程就是一个进程运行时产生了多个线程。
线程对象的生命周期
- 新建状态
- 就绪状态
- 运行状态
- 阻塞状态
- 死亡状态
创建线程
一.继承Thread类
步骤:①、定义类继承Thread;
②、复写Thread类中的run方法;
public class Test3 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
public class Test4 {
public static void main(String[] args) {
Test3 test3 = new Test3();
test3.start();
}
}
注意.run() 不会启动线程,只是普通的调用方法而已。不会分配新的分支栈。(这种方式就是单线程。)
t.start() 方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。
启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
实现Runnable接口:
实现步骤: ①、定义类实现Runnable接
②、覆盖Runnable接口中的run方法,将线程要运行的代码放在该run方法中。
③、通过Thread类建立线程对象。
④、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程执行指定对象的run方法就要先明确run方法所属对象。
⑤、调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
public class Test3 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
public class Test4 {
public static void main(String[] args) {
Thread thread = new Thread(new Test3());
thread.start();
}
}
3、通过Callable和Future创建线程:
实现步骤:①、创建Callable接口的实现类,并实现call()方法,改方法将作为线程执行体,且具有返回值。
②、创建Callable实现类的实例,使用FutrueTask类进行包装Callable对象,FutureTask对象封装了Callable对象的call()方法的返回值
③、使用FutureTask对象作为Thread对象启动新线程。
④、调用FutureTask对象的get()方法获取子线程执行结束后的返回值。
public class Test3 implements Callable {
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 0; i < 10; i++) {
sum+=i;
}
return sum;
}
}
public class Test4 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Test3 test3 = new Test3();
ExecutorService es = Executors.newFixedThreadPool(1);
Future<Integer> future = es.submit(test3);
Integer i = future.get();
System.out.println(i);
}
}
继承Thread类和实现Runnable接口、实现Callable接口的区别。
继承Thread:
优点:编写简单,可直接用this.getname()获取当前线程,不必使用Thread.currentThread()方法。
缺点:已经继承了Thread类,无法再继承其他类。
实现Runnable:
优点:避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
缺点:比较复杂、访问线程必须使用Thread.currentThread()方法、无返回值。
实现Callable:
优点:有返回值、避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
缺点:比较复杂、访问线程必须使用Thread.currentThread()方法
在实际开发过程中一般使用实现Runnable的方式创建多线程。
获取当前线程对象、获取线程对象名字、修改线程对象名字
方法名 | 作用 |
---|---|
static Thread currentThread() | 获取当前线程对象 |
String getName() | 获取线程对象名字 |
void setName(String name) | 修改线程对象名字 |
当线程没有设置名字的时候,默认的名字是什么?
- Thread-0
- Thread-1
- Thread-2
- Thread-3
关于线程的sleep方法
方法名 | 作用 |
---|---|
static void sleep(long millis) | 让当前线程休眠millis秒 |
静态方法:Thread.sleep(1000);
参数是毫秒
作用: 让当前线程进入休眠,进入“阻塞状态”,放弃占有cpu时间片,让给其它线程使用。
这行代码出现在A线程中,A线程就会进入休眠。
这行代码出现在B线程中,B线程就会进入休眠。
Thread.sleep()方法,可以做到这种效果:
间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。
关于线程中断sleep()的方法
方法名 | 作用 |
---|---|
void interrupt() | 终止线程的睡眠 |
Java进程的优先级
方法名 | 作用 |
---|---|
int getPriority() | 获得线程优先级 |
void setPriority(int newPriority) | 设置线程优先级 |
线程安全
当多个线程访问是就可能会出现线程安全
一个多窗口买票:
public class MP implements Runnable{
private static int a=100;
private Object obj = new Object();
@Override
public void run() {
while (true){
if (a > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printstacktrace();
}
System.out.println(Thread.currentThread().getName() + "票号" + a);
a--;
}
}
}
}
public class Test3 {
public static void main(String[] args) {
MP mp = new MP();
Thread thread1 = new Thread(mp,"一号");
Thread thread2 = new Thread(mp,"二号");
Thread thread3 = new Thread(mp,"三号");
Thread thread4 = new Thread(mp,"四号");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
我们可以看到,这里出现了一些重复票,为什么会出现这种情况,出现这种情况显然表明我们这个方法根本就不是线程安全的,出现这种问题的原因有很多,我们说最常见的一种,就是我们A线程在进入方法后,拿到了a的值,刚把这个值读取出来还没有改变a的值的时候,结果线程B也进来的,那么导致线程A和线程B拿到的a值是一样的。
多线程安全问题解决
同步代码块
格式:
synchronized (锁对象) {
可能会产生线程安全问题的代码
}
public class MP implements Runnable{
private static int a=100;
private Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (obj) {
if (a > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printstacktrace();
}
System.out.println(Thread.currentThread().getName() + "票号" + a);
a--;
}
}
}
}
}
可以看到没有了重复票
提示:加上同步后只有一个线程在执行是cpu太好了,可以加大基数就可以看到多个线程。
JDK1.5后的同步解决
在JDK5之前,锁的获取和释放都是隐式的,看不见。也就说我们并没有直接看到在哪里加上了锁,在哪里释放了锁。到了JDK5的时候,java中提供了一个Lock接口,在这个接口中定义了锁的释放和获取的方法,后期程序中需要使用同步,这时可以使用Lock接口的实现类显示的完成锁的获取和释放的动作。
public void lock()
:加同步锁。
public void unlock()
:释放同步锁。
注意:在使用Lock锁对象时,需要手动的书写代码用来:获取锁、释放锁
由于Lock属于接口,不能创建对象,所以我们可以使用它的子类reentrantlock来创建对象并使用Lock接口中的函数。
用Lock改造上述买票后代码
public class MP implements Runnable{
private static int a=100;
private Object obj = new Object();
Lock l=new reentrantlock();
@Override
public void run() {
while (true){
l.lock();
if (a > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printstacktrace();
}
System.out.println(Thread.currentThread().getName() + "票号" + a);
a--;
}
l.unlock();
}
}
}
同步的前提:
1、必须要有两个或者两个以上的线程。
2、必须是多个线程使用同一个锁。
3、必须保证同步中只能有一个线程在运行。
4、只能同步方法,不能同步变量和类。
6、如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
死锁
进程A中包含资源A,进程B中包含资源B,A的下一步需要资源B,B的下一步需要资源A,所以它们就互相等待对方占有的资源释放,所以也就产生了一个循环等待死锁。
public class DeadLock {
public static void main(String[] args) {
Thread t1 = new Thread(new DeadLockTest(true));
Thread t2 = new Thread(new DeadLockTest(false));
t1.start();
t2.start();
}
}
class DeadLockTest implements Runnable {
private boolean flag;
static Object obj1 = new Object();
static Object obj2 = new Object();
public DeadLockTest(boolean flag) {
this.flag = flag;
}
public void run() {
if (flag) {
synchronized (obj1) {
System.out.println("if lock1");
synchronized (obj2) {
System.out.println("if lock2");
}
}
} else {
synchronized (obj2) {
System.out.println("else lock2");
synchronized (obj1) {
System.out.println("else lock1");
}
}
}
}
}
注意:在开发中一旦发生了死锁现象,不能通过程序自身解决。必须修改程序的源代码。
在开发中,死锁现象可以避免,但不能直接解决。当程序中有多个线程时,并且多个线程需要通过嵌套对象锁(在一个同步代码块中包含另一个同步代码块)的方式才可以操作代码,此时就容易出现死锁现象。
可以使用一个同步代码块解决的问题,不要使用嵌套的同步代码块,如果要使用嵌套的同步代码块,就要保证同步代码块的上的对象锁使用同一个对象锁(唯一的对象锁)
原文地址:https://www.jb51.cc/wenti/3282295.html
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。