Java多线程(上)
1.认识线程和进程
-
什么是线程
- 线程就是程序执行的一条路径,例如QQ,同时多个窗口聊天。
-
什么是进程
- 进程是程序的一次动态执行过程,例如你用电脑打开QQ。
-
线程与进程之间的关系
- 一个进程可以包含多条进程,例如你打开QQ应用是开启了一个进程,使用QQ开启多个聊天窗口聊天相当于开启了多条线程。
-
多线程
- 线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
- 多进程是指操作系统能同时运行多个任务(程序)。
- 多线程是指在同一程序中有多个顺序流在执行。
2.线程实现的两种方式以及区别
继承Thread方法实现线程
public static void main(String[]args){
MyThread mt=new MyThread();//4.创建Thread类的子类
mt.start();//5.开启线程
for(int i=0;i<1000;i++){
System.out.println("bb");
}
}
}
class MyThread extends Thread{//1.继承Thread
public void run(){//2.重写run方法
for(int i=0;i<1000;i++){//3.将要执行的代码写在run方法里
System.out.println("aaaaaaaaa");
}
}
}
调用Runnable接口实现线程
public class Demo4_Ticket {
public static void main(String[] args) {
MyTicket mt=new MyTicket();//创建线程对象。
new Thread(mt).start();//执行线程。
}
}
class MyTicket implements Runnable{//继承Runnable接口
@Override
public void run() {
//在这里写线程执行的代码。
}
}
Thread方法和Runnable接口实现线程的区别
-
继承Thread类
- 优点:可以直接使用Thread类中的方法,代码简单。
- 弊端:如果已经有了父类,则不能使用,不适合资源共享。
-
实现Runnable接口
-
优点:即使有了父类也可以实现接口,而且接口是可以多实现的,代码可以被多个线程共享。
-
弊端:不能直接使用Thread中的方法,需要先获取线程对象后,才能得到Thread的方法,代码复杂。
-
3.匿名内部类创建线程的方法
//匿名内部类创建线程方法一
new Thread(){//1.继承Thread类
@Override
public void run(){
for(int i=0;i<1000;i++){
System.out.println("aaa");
}
}
}.start();
//匿名内部类创建线程方法二
new Thread(new Runnable(){//1.将runnnable的子类对象传递给Thread构造函数
@Override
public void run(){//2.重写run方法
for(int i=0;i<1000;i++){
System.out.println("bbbbbbbb");
}
}
}).start();//3.开启线程
获取,更改线程名称
//获取线程名称
new Thread("小胖一号"){
@Override
public void run() {
System.out.println(this.getName());
}
}.start();
设置线程名称:
//方法一
new Thread("小胖一号"){
@Override
public void run() {
this.setName("NOE");
System.out.println(this.getName());
}
}.start();
//方法二
Thread t1=new Thread(){
@Override
public void run() {
System.out.println(this.getName());
}
};
t1.setName("TWO");
t1.start();
4.线程状态转换
先理解一下线程的五种状态,下图中运行和运行中是一种状态。
- 睡眠线程:Thread.sleep(1000)//括号中的单位是毫秒。
new Thread(){
@Override
public void run() {
for (int i = 1;; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("小胖,胖了"+i+"圈");
}
}
}.start();
- 守护线程:setDeamon(true)设置该线程为守护线程,守护线程不会单独执行,非守护线程都执行完毕时,自动退出。
Thread t1=new Thread(){
@Override
public void run() {
for (int i = 0; i < 2; i++) {
System.out.println(getName()+"aaaaaaaaaaaaaaaaaaa");
}
}
};
Thread t2=new Thread(){
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(getName()+"bbbb");
}
}
};
t2.setDaemon(true);
t1.start();
t2.start();
- 加入线程:join() 插队执行,先执行插入的线程,再执行其他线程
Thread t1=new Thread(){
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println("aaaaaaaaaa");
}
}
};
Thread t2=new Thread(){
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
if (i==2){
try {
// t1.join(); 插队执行,先执行插入的线程,再执行其他线程
t1.join(1);//插队指定的时间,超过指定的后,线程交替执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("bb");
}
}
};
t1.start();
t2.start();
-
礼让线程:yieId()礼让线程,哪个线程调用,那该线程则让出CPU。
-
设置线程的优先级:线程名.setPriority(10);//设置线程执行的优先级,优先级为1~10,10的优先级最高
Thread t1=new Thread(){ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("aaaaaa"); } } }; Thread t2=new Thread(){ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("bb"); } } }; t1.setPriority(10);//设置线程执行的优先级,优先级为1~10,10的优先级最高 t1.start(); t2.start();
-
线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。
5.同步代码块
- 1.什么情况下需要同步
- 当多线程并发,有多段代码执行时,我们希望某一段代码执行的过程中CPU不要切换到其他线程工作,这时就需要同步
- 如果两段代码是同步的,那么同一时间只能执行一段代码,在这一段代码执行结束之前,不会执行另外一段代码。
- 2.同步代码块
- 使用Synchronized关键字加上一个锁对象来定义一段代码,这就是同步代码块。
- 多个同步代码块如果使用的是相同的锁对象,那么他们就是同步的。
//其中锁的对象要是同一个。
public class Synchronized {
/*
同步代码块
*/
public static void main(String[] args) {
Printer p=new Printer();
new Thread(){
@Override
public void run() {
while(true){
p.print1();
}
}
}.start();
new Thread(){
@Override
public void run() {
while(true){
p.print2();
}
}
}.start();
}
}
class Printer{
demo d=new demo(); //同步代码块,锁机制,对象可以是任意的
public void print1(){
synchronized (d){
System.out.print("黑");
System.out.print("马");
System.out.println();
}
}
public void print2(){ //同步代码块,不能用匿名对象,因为不是同一个对象,锁不同
synchronized(d){
System.out.print("1");
System.out.print("2");
System.out.print("3");
System.out.print("4");
System.out.print("5");
System.out.print("6");
System.out.print("7");
System.out.print("8");
System.out.println();
}
}
}
6.同步方法
使用synchronized关键字修饰的一个方法,该方法中所有的代码都是同步的。
public class Synchronized {
/*
同步代码块
*/
public static void main(String[] args) {
Printer p=new Printer();
new Thread(){
@Override
public void run() {
while(true){
p.print1();
}
}
}.start();
new Thread(){
@Override
public void run() {
while(true){
p.print2();
}
}
}.start();
}
}
class Printer{
demo d=new demo(); //同步方法只用在方法上加synchronized关键字
//非静态同步方法的锁对象是什么?
//答:非静态的同步方法的锁对象是this
//静态的同步方法的锁对象是什么?
//是该类的字节码对象。
public static synchronized void print1(){
System.out.print("黑");
System.out.print("马");
System.out.println();
}
public static void print2(){ //
synchronized(Printer.class){
System.out.print("1");
System.out.print("2");
System.out.print("3");
System.out.print("4");
System.out.print("5");
System.out.print("6");
System.out.print("7");
System.out.print("8");
System.out.println();
}
}
}
7.线程安全问题
实例:
方法一:使用继承Thread,实现线程
public class Demo3_Ticket {
public static void main(String[] args) {
new Ticket().start();
new Ticket().start();
new Ticket().start();
new Ticket().start();
}
}
class Ticket extends Thread{
private static int ticket=100;
public void run(){
while(true){
synchronized(Ticket.class){//Ticket的字节码对象
if (ticket==0){
break;
}
System.out.println(getName()+"...这是第"+ticket--+"张票");
}
}
}
}
方法二:使用Runnable接口,实现线程
public class Demo4_Ticket {
public static void main(String[] args) {
MyTicket mt=new MyTicket();
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
// Thread t1=new Thread(mt); 多次启动线程是非法的
// t1.start();
// t1.start();
// t1.start();
// t1.start();
}
}
class MyTicket implements Runnable{
private int tickets=100;
@Override
public void run() {
while(true){
synchronized(this){
if (tickets<=0){
break;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"...这是第"+tickets--+"张票");
}
}
}
}
8.死锁实例
避免死锁的发生,Synchronized最好不要嵌套
public class Demo5 {
private static String s1="筷子左";
private static String s2="筷子右";
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
while(true){
synchronized (s1){
System.out.println("获取"+s1+"等待"+s2);
synchronized (s2){
System.out.println("拿到"+s2+"开吃");
}
}
}
}
}.start();
new Thread(){
@Override
public void run() {
while(true){
synchronized (s2){
System.out.println(getName()+"...获取"+s2+"等待"+s1);
synchronized (s1){
System.out.println("拿到"+s1+"开吃");
}
}
}
}
}.start();
new Thread(){
@Override
public void run() {
while(true){
synchronized (s2){
System.out.println(getName()+"...获取"+s2+"等待"+s1);
synchronized (s1){
System.out.println("拿到"+s1+"开吃");
}
}
}
}
}.start();
}
}