1.什么是多线程
首先引入程序与进程概念:
-
程序(program)
程序是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码(还没有运行起来),静态对象。
-
进程(process)
进程是程序的一次执行过程,也就是说程序运行起来了,加载到了内存中,并占用了cpu的资源。这是一个动态的过程:有自身的产生、存在和消亡的过程,这也是进程的生命周期。
进程是系统资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。
-
线程(thread)
进程可进一步细化为线程,是一个程序内部的执行路径:线程是以cpu调度分配的单位。
若一个进程同一时间并行执行多个线程,那么这个进程就是支持多线程的。
【当然,一个进程最少包括一个线程,要不然无意义】
2.线程创建的几种方法
2.1创建方式一:继承Thread
public class MyThread extends Thread{
}
继承Thread重写run方法:
public class MyThread extends Thread{
public void run(){
}
}
这个run,当主线程运行时就会执行run里面的程序【注:main方法就是主线程】
这里举例:
public class MyThread extends Thread{
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println("我是run线程=="+i);
}
}
}
在run中我写上for循环,以便一会演示而用。
--------
新建测试类并声明main方法
随后创建MyThread类对象
并调用start()方法启用run线程
例:
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
我在main方法中同样写上一个for循环
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i = 0; i < 10; i++) {
System.out.println("我是main线程=="+i);
}
}
此时运行,run线程与主线程同为竞争管理
因为他们会同时抢夺cpu调度资源
至于哪个线程抢的多哪个线程抢的少就全凭运气
运行结果:
确实随机...
2.1.1 线程的命名和获取
首先是获取当前线程名称:
getName();
System.out.println(this.getName()+"我是run线程=="+i);
这里可以直接使用this用来指代当前new对象
当然,如果你不先给线程设置名称的话就会使用默认名称,以 Thread-? 的形式
这里再次使用 setName() 来为线程命名
MyThread myThread = new MyThread();
myThread.setName("线程A-");//为线程命名
myThread.start();
再次运行:
当然,getName() 虽说好用,但是他有缺陷
他只能被用于继承Thread的子类身上
你把 getName() 用在main身上就不行了
所以推出另一种万能获取线程名称的方法
Thread.currentThread().getName()
这个不管是什么牛马,都可Thread加身,非常的牛13
System.out.println(Thread.currentThread().getName()+"我是main线程=="+i);
这里直接加在main中,效果可见:
2.2 创建方式二:实现 Runnable 接口
public class MyThread2 implements Runnable{
}
public class MyThread2 implements Runnable{
public void run() {
}
}
我还是在其中写上一个for循环
public class MyThread2 implements Runnable{
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我是run线程=="+i);
}
}
}
在main方法中同样写上for循环
现在的话不单单是直接new MyThread2就能行的
还得new Thread才阔以,并将对象传入Thread中,同时还可以有第二个参数,可以直接为此线程命名
MyThread2 myThread2 = new MyThread2();
Thread thread = new Thread(myThread2,"窗口1");
剩下的直接运行,与第一种方法相同
优缺点:
- 第一种确实方便,但是不利于效率,因为只能继承一个,也不能重写什么接口了,限制很大
- 第二种相对于麻烦了一步,但是它可以同时继承,同时重写多个接口
3.线程卖票例子
3.1继承Thread方式卖票
新建MyThread3并继承Thread重写run方法
并定义变量ticket票数等于100
public class MyThread3 extends Thread{
private int ticket = 100;
@Override
public void run() {
while (ticket > 0){
ticket--;
System.out.println(Thread.currentThread().getName()+"卖了1张票,还剩"+ticket+"票");
}
}
}
再次新建测试类并什么main方法中创建三个窗口同时卖票,【注意,是三个窗口同时各卖100张票】,创建三个窗口也就意味着是创建三个线程
public static void main(String[] args) {
MyThread3 myThread3 = new MyThread3();
myThread3.setName("窗口一");
myThread3.start();
MyThread3 myThread4 = new MyThread3();
myThread4.setName("窗口二");
myThread4.start();
MyThread3 myThread5 = new MyThread3();
myThread5.setName("窗口三");
myThread5.start();
}
运行测试:
太多了,太多了,太多了这里盛不下省略..................................
3.2 实现接口Runnable 方式卖票
新建MyThread4实现 Runnable 接口并重写run方法,
并定义变量ticket票数等于100
public class MyThread4 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (ticket > 0){
ticket--;
System.out.println(Thread.currentThread().getName()+"卖了1张票,还剩"+ticket+"票");
}
}
}
新建测试类文件并声明main方法中创建三个窗口同时卖100张票,没错,你没看错,是三个窗口同时卖同100张票
public static void main(String[] args) {
MyThread4 myThread4 = new MyThread4();
Thread thread = new Thread(myThread4,"窗口1");
thread.start();
Thread thread2 = new Thread(myThread4,"窗口2");
thread2.start();
Thread thread3 = new Thread(myThread4,"窗口3");
thread3.start();
}
运行测试:
你会发现问题的
无论是继承Thread的方式卖票,还是重写 Runnable 方法的方式卖票,结果都有一个共同点,三个窗口会重复卖掉同一张票,并且会使得库存呈现负数,这是极其不合理的
所以,这里就开始涉及到 线程安全 的方向
4.线程安全--锁的概念
4.1自动锁 synchronized(共享资源){}
这一概念用大白话来说就是一间厕所只有一个坑位,但是这个时候却有三个人要嘘嘘,当然不可能三个人同时对着坑位嘘嘘,这个时候某一个人率先出手抢到了坑位,然后给厕所门一锁
另外两人只能等在门后,待某人解决后开门,临到下一人,继续关门上锁嘘嘘,循环往复,自动锁的概念就此简单化
当然,在上锁之前,我们的代码需要简单改动一下在上锁
public class MyThread4 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
synchronized(this){
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket--;
System.out.println(Thread.currentThread().getName()+"卖了1张票,还剩"+ticket+"票");
}
else {
break;
}
}
}
}
}
如果你在测试的时候发现效果不明显,那就加上 Thread.sleep(100); 这个方法,效果杠杠的
至于什么作用,稍后说道说道
4.2 手动锁 lock
演示,效果跟 那个自动锁一样,只是变成了手动
首先就是new 一个 ReentrantLock();
public static ReentrantLock lock = new ReentrantLock();
public class MyThread4 implements Runnable{
private int ticket = 100;
public static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();//手动开启锁
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket--;
System.out.println(Thread.currentThread().getName()+"卖了1张票,还剩"+ticket+"票");
}
else {
break;
}
lock.unlock();//手动关闭锁
}
}
}
测试运行:效果奇好
这样大大有利于线程安全的漏洞
5.Thread类的常用方法
- start() : 启动当前线程, 调用当前线程的run()方法
- run() : 通常需要重写Thread类中的此方法, 将创建的线程要执行的操作声明在此方法中
- currentThread() : 静态方法, 返回当前代码执行的线程
- getName() : 获取当前线程的名字
- setName() : 设置当前线程的名字
- yield() : 释放当前CPU的执行权
- join() : 在线程a中调用线程b的join(), 此时线程a进入阻塞状态, 知道线程b完全执行完以后, 线程a才结束阻塞状态
- stop() : 已过时. 当执行此方法时,强制结束当前线程.
- sleep(long militime) : 让线程睡眠指定的毫秒数,在指定时间内,线程是阻塞状态
这里方法太多,就不一一测试了,感兴趣的小伙伴可以尝试着调用其中的方法,看看是什么效果
---------
以上便是java高级之线程中的部分内容,如有漏缺请在下方留言告知,我会及时补充
标签:java,Thread,高级,线程,new,run,ticket,多线程,public From: https://www.cnblogs.com/9--1/p/17629466.html