线程:
是进程里面执行的最小单位,可完成一个独立的循环进程Java虚拟机允许应用程序并发地执行多个线程没个线程都有一个优先级,高优先级的线程优先执行与低线程优先级
1:进程的状态:
1):就绪状态(Ready)
2):运行状态(Running)
3):阻塞状态(Blocked)
多线程:
在一个进程中存在多个线程同时运行,这称为多线程
优点:充分利用CPU的资源
简化编程模型
良好的用户体验
主线程:
- Thread类
- 主线程
main()方法即为主线程入口
产生其他子线程的进程
必须最后完成,因为他需要执行各种关闭操作
public class ThreadDemo01 {
public static void main(String[] args) {
//main()方法是线程的主入口,是一个线程
//获取当前的线程对象
Thread thread=Thread.currentThread();
System.out.println("当前的线程对象 "+thread);
//获取当前线程名称
String name =thread.getName();
System.out.println(name);
//获取线程优先级
int priority=thread.getPriority();
System.out.println(priority);
//设置线程的名字
thread.setName("小丑");
System.out.println("线程名称 "+thread.getName());
System.out.println("线程的最高级为: "+thread.MAX_PRIORITY);
System.out.println("线程的最低级为: "+thread.MIN_PRIORITY);
System.out.println("线程的默认级为: "+thread.NORM_PRIORITY);
}
}
运行结果:
当前的线程对象 Thread[main,5,main]
main
5
线程名称 小丑
线程的最高级为: 10
线程的最低级为: 1
线程的默认级为: 5
线程的创建
- 通过继承Thread类
- 通过实现Runnable接口实现
线程启动的步骤:定义线程--->创建线程对象--->启动线程--->启动线程
继承Thread类
- 创建MyThread类并继承Thread类
- 重写Thread中的run()方法
- 创建MyThread类对象并调用start()方法启动线程
public class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
public MyThread() {
}
/**
* 将类定义成线程类,可以通过Thread类
*/
public void run(){
for (int i = 0; i <20; i++) {
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}
public class Test {
public static void main(String[] args) {
MyThread mt=new MyThread("小丑");
MyThread mt1=new MyThread("占卜家");
//start()是开始线程的而不是调用run()方法
mt.start();
mt1.start();
}
}
实现Runnable接口
- 创建MyRunnable类并实现Runnaable接口
- 实现run()方法,执行线程执行体
- 调用start()方法启动线程
public class MyRunnable implements Runnable{
//避免单继承
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}
public class Test {
public static void main(String[] args) {
MyRunnable mr=new MyRunnable();
//重新调用Thread里面的构造,传入MyRunnable类型的接口
//通过实现Runnable接口,来实现Thread的构造方法
Thread th=new Thread(mr,"你");
Thread th1=new Thread(mr,"我");
th.start();
th1.start();
}
}
两类线程的比较:
继承Thread类:比较简单,可以直接调用对象操作
实现Runnable接口:可以避免单继承,可以实现更多的资源共享
线程的状态
线程休眠
线程的休眠,目的是让出CPU的时间,让其他的线程可以执行,但不会释放对象锁,同时线程不会让出任何显示器的所有权
线程被中断后,会抛出InterruptedException异常,当前线程的中断状态会被清除.
public class ThreadDemo01 {
public static void main(String[] args) {
MyThread mt=new MyThread("小丑");
//start()是开始线程的而不是调用run()方法
MyRunnable mr=new MyRunnable();
Thread mt1=new Thread(mr,"占卜家");
mt.start();
mt1.start();
}
}
/**
* 继承Thread类的线程
*/
class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
public MyThread() {
}
//将类定义成线程类,可以通过Thread类
public void run(){
for (int i = 0; i <20; i++) {
System.out.println(Thread.currentThread().getName()+"-"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 实现Runnable接口实现的线程
*/
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
join方法和中断线程
join方法:
加入线程,让调用的线程先执行指定时间或执行完毕 //强制执行
中断线程:
1:使用interrput方法中断线程,但是会清除中断线程,需要设置一个中断状态
2:自定义中断线程(更加推荐使用)
public class ThreadDemo02 {
public static void main(String[] args) {
MyRunnable1 mr=new MyRunnable1();
Thread t=new Thread(mr,"小丑");
// t.start();
MyRunnable2 mr2=new MyRunnable2();
Thread t2=new Thread(mr2,"占卜家");
t2.start();
for (int i = 0; i <50; i++) {
System.out.println(Thread.currentThread().getName()+"-"+i);//main方法的线程
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i==20){
/*try {
t.join();//让t线程执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
}*/
// t.interrupt();
mr2.flag=false;//可以调用自定义的异常终止程序
}
}
}
}
class MyRunnable1 implements Runnable{
@Override
public void run() {
for (int i = 0; i <50; i++) {
if(Thread.interrupted()){//清除中断状态,会把中断状态清除
break;
}
System.out.println(Thread.currentThread().getName()+"-"+i);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
}
class MyRunnable2 implements Runnable{
boolean flag=true;
public MyRunnable2(){
flag=true;
}
@Override
public void run() {
int i=0;
while(flag){
System.out.println(Thread.currentThread().getName()+"--"+(i++));
try {
Thread.sleep(300);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
yiled()方法
会提供一次其它线程去占用cpu资源的机会,但是本身也会去占用cpu的资源
public class YieldTest {
public static void main(String[] args) {
MyRunnable mr=new MyRunnable();
Thread tr=new Thread(mr);
tr.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==5){
System.out.print(Thread.currentThread().getName()+"线程礼让 ");
Thread.yield();
}
}
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==5){
System.out.print(Thread.currentThread().getName()+"线程礼让 ");
Thread.yield();
}
}
}
}
运行结果:
main 0
main 1
main 2
main 3
main 4
main 5
main线程礼让 Thread-0 0//本该是main线程运行,确由Thread-0去运行
Thread-0 1
Thread-0 2
main 6
Thread-0 3
main 7
main 8
main 9
Thread-0 4
Thread-0 5
Thread-0线程礼让 Thread-0 6
Thread-0 7
Thread-0 8
Thread-0 9
多线程共享调度问题及解决
在线程多线程调度的时候.会出现在抢占CPU资源的时候导致原本的输出信息错误
public class SynchronizedTest {
public static void main(String[] args) {
Piao piao=new Piao();
Thread tr=new Thread(piao,"张三");
Thread tr1=new Thread(piao,"李四");
Thread tr2=new Thread(piao,"王二");
tr.start();
tr1.start();
tr2.start();
}
}
class Piao implements Runnable{
//买到票的数量
public int num=10;
//剩余票的数量
public int count=0;
@Override
public void run() {
while(true){
if(num<=0){
break;
}
num--;//没买一次票数量加一
count++;//没买一次票,票的数目减一
try {
Thread.sleep(500);//模拟网络延迟500ms
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+"抢到第"+count+"张票,剩余"+num+"张票");
}
}
}
运行结果:
李四抢到第3张票,剩余7张票
王二抢到第3张票,剩余7张票
张三抢到第3张票,剩余7张票
李四抢到第6张票,剩余4张票
王二抢到第6张票,剩余4张票
张三抢到第6张票,剩余4张票
王二抢到第9张票,剩余1张票
张三抢到第9张票,剩余1张票
李四抢到第9张票,剩余1张票
王二抢到第10张票,剩余0张票
结论:结果可以表面,有人同时抢到了第3张票,剩余的票数也会出现相同,发生就这错误就是因为多线程在抢占CPU资源的时候不考虑到线程的执行是否结束就已经被下一个线程所占用,从而导致原本线程输出的信息发生了偏差
解决方案:
运用synchronized
作用:在线程执行的时候相当于加了一把锁,该线程的运行结果会返回给线程中去,
而不会被其他线程占用后改变线程的运行结果
1:使用synchronized修饰的方法控制对类成员变量的访问
访问修饰符 synchronized 返回类型 方法名(参数列表){……}
或者
synchronized 访问修饰符 返回类型 方法名(参数列表){……
2;使用synchronized关键字修饰的代码块
这里举例了第二种解决方案的代码示例:
public class SynchronizedTest {
public static void main(String[] args) {
Piao piao=new Piao();
Thread tr=new Thread(piao,"张三");
Thread tr1=new Thread(piao,"李四");
Thread tr2=new Thread(piao,"王二");
tr.start();
tr1.start();
tr2.start();
}
}
class Piao implements Runnable{
//剩余票的数量
public int num=200;
//买到票的数量
public int count=0;
@Override
public void run() {
while(true){
synchronized(this){
if(num<=0){
break;
}
num--;//没买一次票数量减一
count++;//没买一次票,票的数目加一
try {
Thread.sleep(300);//模拟网络延迟500ms
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+"抢到第"+count+"张票,剩余"+num+"张票");
}
}
}
}
面对这种问题,衍生出了线程安全的问题:
方法是否同步 | 效率比较 | 适合场景 | |
线程安全 | 是 | 低 | 多线程并发共享资源 |
非线程安全 | 否 | 高 | 单线程 |
常见的类型对比:
Hashtable和HashMap
Hashtable:
继承关系
实现了Map接口,Hashtable继承Dictionary类
线程安全,效率较低
键和值都不允许为null
HashMap:
继承关系
实现了Map接口,继承AbstractMap类
非线程安全,效率较高
键和值都允许为null
StringBuffer && StringBuilder
前者线程安全,后者非线程安全